diff --git a/.config/suppress.json b/.config/suppress.json new file mode 100644 index 00000000000..9be220b291e --- /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/.dependabot/config.yml b/.dependabot/config.yml deleted file mode 100644 index ea0745fd494..00000000000 --- a/.dependabot/config.yml +++ /dev/null @@ -1,62 +0,0 @@ -version: 1 - -# update_schedule: live is only supported on javascript, ruby:bundler, python, php:composer, dotnet:nuget, rust:cargo, elixir:hex - -update_configs: - - package_manager: "dotnet:nuget" - directory: "/" - update_schedule: "live" - default_labels: - - "CL-BuildPackaging" - ignored_updates: - - match: - dependency_name: "System.*" - - match: - dependency_name: "Microsoft.Win32.Registry.AccessControl" - - match: - dependency_name: "Microsoft.Windows.Compatibility" - - - package_manager: "dotnet:nuget" - directory: "/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility" - update_schedule: "live" - default_labels: - - "CL-BuildPackaging" - ignored_updates: - - match: - dependency_name: "System.*" - - match: - dependency_name: "Microsoft.Win32.Registry.AccessControl" - - match: - dependency_name: "Microsoft.Windows.Compatibility" - - - package_manager: "dotnet:nuget" - directory: "/tools/packaging/projects/reference/System.Management.Automation" - update_schedule: "live" - default_labels: - - "CL-BuildPackaging" - ignored_updates: - - match: - dependency_name: "System.*" - - match: - dependency_name: "Microsoft.Win32.Registry.AccessControl" - - match: - dependency_name: "Microsoft.Windows.Compatibility" - - - package_manager: "dotnet:nuget" - directory: "/test/tools/Modules" - update_schedule: "live" - default_labels: - - "CL-BuildPackaging" - ignored_updates: - - match: - dependency_name: "System.*" - - match: - dependency_name: "Microsoft.Win32.Registry.AccessControl" - - match: - dependency_name: "Microsoft.Windows.Compatibility" - - - package_manager: "dotnet:nuget" - directory: "/src/Modules" - update_schedule: "live" - default_labels: - - "CL-BuildPackaging" diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5c2f381d702..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/dotnet/nightly/sdk:5.0.100-preview.7 +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 36ae9537336..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 5.0, including pwsh (Debian 10)", - "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 cdb71b91aa6..57d2f6c6c3e 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,11 +44,16 @@ 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 dotnet_sort_system_directives_first = true +file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License. + # Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion @@ -97,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 @@ -111,6 +119,17 @@ csharp_prefer_simple_default_expression = true:suggestion dotnet_code_quality_unused_parameters = non_public:suggestion +# Dotnet diagnostic settings: +[*.cs] + +# CA1859: Use concrete types when possible for improved performance +# https://learn.microsoft.com/en-gb/dotnet/fundamentals/code-analysis/quality-rules/ca1859 +dotnet_diagnostic.CA1859.severity = suggestion + +# Disable SA1600 (ElementsMustBeDocumented) for test directory only +[test/**/*.cs] +dotnet_diagnostic.SA1600.severity = none + # CSharp code style settings: [*.cs] diff --git a/.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..14569b81924 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,75 +3,66 @@ # Areas are not limited to the filters defined in this file # First, let's start with areas with no filters or paths +# Default +* @PowerShell/powershell-maintainers + # Area: Performance # @adityapatwardhan -# Area: Portability -# @JamesWTruher - # Area: Security -# @TravisEz13 @PaulHigin -src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @PaulHigin - -# Area: Documentation -.github/ @joeyaiello @TravisEz13 +src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @seeminglyscience -# Area: Test -# @JamesWTruher @TravisEz13 @adityapatwardhan - -# Area: Cmdlets Core -# @JamesWTruher @SteveL-MSFT @anmenaga +# Area: CI Build +.github/workflows @PowerShell/powershell-maintainers @jshigetomi +.github/actions @PowerShell/powershell-maintainers @jshigetomi # Now, areas that should have paths or filters, although we might not have them defined # According to the docs, order here must be by precedence of the filter, with later rules overwritting # but the feature seems to make taking a union of all the matching rules. # Area: Cmdlets Management -src/Microsoft.PowerShell.Commands.Management/ @daxian-dbw @adityapatwardhan +# src/Microsoft.PowerShell.Commands.Management/ @daxian-dbw @adityapatwardhan # Area: Utility Cmdlets -src/Microsoft.PowerShell.Commands.Utility/ @JamesWTruher @PaulHigin +# src/Microsoft.PowerShell.Commands.Utility/ # Area: Console -src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw @anmenaga @TylerLeonhardt - -# Area: Demos -demos/ @joeyaiello @SteveL-MSFT @HemantMahawar +# src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw # Area: DSC -src/System.Management.Automation/DscSupport @TravisEz13 @SteveL-MSFT +# src/System.Management.Automation/DscSupport @TravisEz13 @SteveL-MSFT # Area: Engine # src/System.Management.Automation/engine @daxian-dbw # Area: Debugging # Must be below engine to override -src/System.Management.Automation/engine/debugger/ @PaulHigin +# src/System.Management.Automation/engine/debugger/ # Area: Help -src/System.Management.Automation/help @adityapatwardhan +src/System.Management.Automation/help @adityapatwardhan @daxian-dbw # Area: Intellisense # @daxian-dbw # Area: Language -src/System.Management.Automation/engine/parser @daxian-dbw +src/System.Management.Automation/engine/parser @daxian-dbw @seeminglyscience # Area: Providers -src/System.Management.Automation/namespaces @anmenaga +# src/System.Management.Automation/namespaces # Area: Remoting -src/System.Management.Automation/engine/remoting @PaulHigin +src/System.Management.Automation/engine/remoting @daxian-dbw @TravisEz13 # Areas: Build # Must be last -*.config @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.props @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.yml @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.csproj @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -build.* @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -tools/ @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -docker/ @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin +*.config @PowerShell/powershell-maintainers @jshigetomi +*.props @PowerShell/powershell-maintainers @jshigetomi +*.yml @PowerShell/powershell-maintainers @jshigetomi +*.csproj @PowerShell/powershell-maintainers @jshigetomi +build.* @PowerShell/powershell-maintainers @jshigetomi +tools/ @PowerShell/powershell-maintainers @jshigetomi +# docker/ @PowerShell/powershell-maintainers @jshigetomi # Area: Compliance tools/terms @TravisEz13 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8548101590f..35eab8c9b5b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,47 +1,24 @@ # Contributing to PowerShell -We welcome and appreciate contributions from the community. -There are many ways to become involved with PowerShell: -including filing issues, -joining in design conversations, -writing and improving documentation, -and contributing to the code. -Please read the rest of this document to ensure a smooth contribution process. - -## Intro to Git and GitHub - -* Make sure you have a [GitHub account](https://github.com/signup/free). -* Learning Git: - * GitHub Help: [Good Resources for Learning Git and GitHub][good-git-resources] - * [Git Basics](../docs/git/basics.md): install and getting started -* [GitHub Flow Guide](https://guides.github.com/introduction/flow/): - step-by-step instructions of GitHub Flow - -## Quick Start Checklist +We welcome and appreciate contributions from the community! -* Review the [Contributor License Agreement][CLA] requirement. -* Get familiar with the [PowerShell repository](../docs/git). +There are many ways to become involved with PowerShell including: -## Contributing to Issues +- [Contributing to Documentation](#contributing-to-documentation) +- [Contributing to Issues](#contributing-to-issues) +- [Contributing to Code](#contributing-to-code) -* Review [Issue Management][issue-management]. -* Check if the issue you are going to file already exists in our [GitHub issues][open-issue]. -* If you can't find your issue already, - [open a new issue](https://github.com/PowerShell/PowerShell/issues/new/choose), - making sure to follow the directions as best you can. -* If the issue is marked as [`Up-for-Grabs`][up-for-grabs], - the PowerShell Maintainers are looking for help with the issue. -* Issues marked as [`First-Time-Issue`][first-time-issue], - are identified as being easy and a great way to learn about this project and making - contributions. +Please read the rest of this document to ensure a smooth contribution process. ## Contributing to Documentation -### Contributing to documentation related to PowerShell +Contributing to the docs is an excellent way to get started with the process of making open source contributions with minimal technical skill required. -Please see the [Contributor Guide in `MicrosoftDocs/PowerShell-Docs`](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/CONTRIBUTING.md). +Please see the [Contributor Guide in `MicrosoftDocs/PowerShell-Docs`](https://aka.ms/PSDocsContributor). -#### Quick steps if you're changing an existing cmdlet +Learn how to [Contribute to Docs like a Microsoft Insider](https://www.youtube.com/watch?v=ZQODV8krq1Q) (by @sdwheeler) + +### Updating Documentation for an existing cmdlet If you made a change to an existing cmdlet and would like to update the documentation using PlatyPS, here are the quick steps: @@ -52,70 +29,68 @@ 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 spell checker, follow these steps: -To run the spellchecker, follow these steps: +* install [Node.js](https://nodejs.org/en/) (v10 or up) +* 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. -* install [Node.js](https://nodejs.org/en/) (v6.4.0 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 +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/) (v6.4.0 or up) -* install markdown-link-check by - `npm install -g markdown-link-check@3.7.2` (v3.7.2 **only**) +* install [Node.js](https://nodejs.org/en/) (v10 or up) +* 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 +## Contributing to Issues -Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running-tests-outside-of-ci] on how to test your build locally. +1. Review [Issue Management][issue-management]. +1. Check if the issue you are going to file already exists in our [GitHub issues][open-issue]. +1. If you can't find your issue already, + [open a new issue](https://github.com/PowerShell/PowerShell/issues/new/choose), + making sure to follow the directions as best you can. +1. If the issue is marked as [`Up-for-Grabs`][up-for-grabs], + the PowerShell Maintainers are looking for help with the issue. +1. Issues marked as [`First-Time-Issue`][first-time-issue], + are identified as being easy and a great way to learn about this project and making + contributions. ### Finding or creating an issue @@ -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,21 +261,13 @@ 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. - - You could update the file manually in accordance with messages in the test log file. Or you can use automatically generated file. To get the file you should build the msi package locally: - ```powershell - Import-Module .\build.psm1 - Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration Release -ReleaseTag - Import-Module .\tools\packaging - Start-PSPackage -Type msi -ReleaseTag -WindowsRuntime 'win7-x64' -SkipReleaseChecks - ``` - - Last command will report where new file is located. + You could update the `.spelling` file manually in accordance with messages in the test log file, or + [run the spell checker command-line tool in interactive mode](#spell-checking-documentation) + to add the false-positive words directly. #### Pull Request - Workflow @@ -265,7 +285,7 @@ Additional references: #### Pull Request - Roles and Responsibilities -1. The PR *author* is responsible for moving the PR forward to get it Approved. +1. The PR *author* is responsible for moving the PR forward to get it approved. This includes addressing feedback within a timely period and indicating feedback has been addressed by adding a comment and mentioning the specific *reviewers*. When updating your pull request, please **create new commits** and **don't rewrite the commits history**. This way it's very easy for the reviewers to see diff between iterations. @@ -278,7 +298,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 +317,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 +328,12 @@ Before making changes to the code, first review the [breaking changes contract][breaking-changes-contract] and follow the guidelines to keep PowerShell backward compatible. -## Making Design Changes +### Making Design Changes To add new features such as cmdlets or making design changes, please follow the [PowerShell Request for Comments (RFC)][rfc-process] process. -## Common Engineering Practices +### Common Engineering Practices Other than the guidelines for [coding][coding-guidelines], the [RFC process][rfc-process] for design, @@ -365,7 +385,7 @@ is also appropriate, as is using Markdown syntax. Before you invest a large amount of time, file an issue and start a discussion with the community. -## Contributor License Agreement (CLA) +### Contributor License Agreement (CLA) To speed up the acceptance of any contribution to any PowerShell repositories, you should sign the Microsoft [Contributor License Agreement (CLA)](https://cla.microsoft.com/) ahead of time. @@ -379,11 +399,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 +421,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 2c51890e890..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: Issue-Question -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 ff089662700..00000000000 --- a/.github/ISSUE_TEMPLATE/Distribution_Request.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: Distribution Support Request -about: Requests support for a new distribution -title: "Distribution Support Request" -labels: Distribution-Request -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): -- [ ] **Required** - An issues has been filed to create a Docker image in https://github.com/powershell/powershell-docker -- 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/3.0/3.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 - 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 cf91c6e3dc3..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 -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 1932bff5c6d..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 -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/Security_Issue_Report.md b/.github/ISSUE_TEMPLATE/Security_Issue_Report.md deleted file mode 100644 index f2304882dc3..00000000000 --- a/.github/ISSUE_TEMPLATE/Security_Issue_Report.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Security Issue -about: Security Issue -title: "!!! READ TEMPLATE COMPLETELY FIRST!!!" -labels: Area-Security -assignees: 'TravisEz13' - ---- - -## Security Issue - -Excerpt from [Issue Management - Security Vulnerabilities](https://github.com/PowerShell/PowerShell/blob/master/.github/SECURITY.md) - -> 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 has confirmed filing an issue is appropriate. - -When you have permission from [secure@microsoft.com](https://technet.microsoft.com/security/ff852094.aspx) to file an issue here, -please use the Bug Report template and state in the description that you are reporting the issue in coordination with [secure@microsoft.com](https://technet.microsoft.com/security/ff852094.aspx). diff --git a/.github/ISSUE_TEMPLATE/WG_member_request.yaml b/.github/ISSUE_TEMPLATE/WG_member_request.yaml new file mode 100644 index 00000000000..1d7f0e9ba53 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/WG_member_request.yaml @@ -0,0 +1,66 @@ +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 efbe076a8b9..27089847987 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,30 +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 enumerated concerns in the summary. 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). - - Impact on Completions (both in the console and in editors) - one of PowerShell's most powerful features. - - Impact on [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer) (which provides linting & formatting in the editor extensions). - - Impact on [EditorSyntax](https://github.com/PowerShell/EditorSyntax) (which provides syntax highlighting with in VSCode, GitHub, and many other editors). + - [ ] 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..797f7003851 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,12 +1,39 @@ -# 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). + +You should receive a response within 24 hours. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * 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..65331fb3185 --- /dev/null +++ b/.github/actions/build/ci/action.yml @@ -0,0 +1,40 @@ +name: CI Build +description: 'Builds PowerShell' +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module .\tools\ci.psm1 + Show-Environment + shell: pwsh + - name: Set Build Name for Non-PR + if: github.event_name != 'PullRequest' + run: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" + shell: pwsh + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + global-json-file: ./global.json + - name: Bootstrap + if: success() + run: |- + Write-Verbose -Verbose "Running Bootstrap..." + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" + shell: pwsh + - name: Build + if: success() + run: |- + Write-Verbose -Verbose "Running Build..." + Import-Module .\tools\ci.psm1 + Invoke-CIBuild + shell: pwsh + - name: Upload build artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: build + path: ${{ runner.workspace }}/build diff --git a/.github/actions/infrastructure/get-changed-files/README.md b/.github/actions/infrastructure/get-changed-files/README.md new file mode 100644 index 00000000000..277b28c0674 --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/README.md @@ -0,0 +1,122 @@ +# Get Changed Files Action + +A reusable composite action that retrieves the list of files changed in a pull request or push event. + +## Features + +- Supports both `pull_request` and `push` events +- Optional filtering by file pattern +- Returns files as JSON array for easy consumption +- Filters out deleted files (only returns added, modified, or renamed files) +- Handles up to 100 changed files per request + +## Usage + +### Basic Usage (Pull Requests Only) + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process files + run: | + echo "Changed files: ${{ steps.changed-files.outputs.files }}" + echo "Count: ${{ steps.changed-files.outputs.count }}" +``` + +### With Filtering + +```yaml +# Get only markdown files +- name: Get changed markdown files + id: changed-md + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '*.md' + +# Get only GitHub workflow/action files +- name: Get changed GitHub files + id: changed-github + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '.github/' +``` + +### Support Both PR and Push Events + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + with: + event-types: 'pull_request,push' +``` + +## Inputs + +| Name | Description | Required | Default | +|------|-------------|----------|---------| +| `filter` | Optional filter pattern (e.g., `*.md` for markdown files, `.github/` for GitHub files) | No | `''` | +| `event-types` | Comma-separated list of event types to support (`pull_request`, `push`) | No | `pull_request` | + +## Outputs + +| Name | Description | +|------|-------------| +| `files` | JSON array of changed file paths | +| `count` | Number of changed files | + +## Filter Patterns + +The action supports simple filter patterns: + +- **Extension matching**: Use `*.ext` to match files with a specific extension + - Example: `*.md` matches all markdown files + - Example: `*.yml` matches all YAML files + +- **Path prefix matching**: Use a path prefix to match files in a directory + - Example: `.github/` matches all files in the `.github` directory + - Example: `tools/` matches all files in the `tools` directory + +## Example: Processing Changed Files + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process each file + shell: pwsh + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.files }} + run: | + $changedFilesJson = $env:CHANGED_FILES + $changedFiles = $changedFilesJson | ConvertFrom-Json + + foreach ($file in $changedFiles) { + Write-Host "Processing: $file" + # Your processing logic here + } +``` + +## Limitations + +- Simple filter patterns only (no complex glob or regex patterns) + +## Pagination + +The action automatically handles pagination to fetch **all** changed files in a PR, regardless of how many files were changed: + +- Fetches files in batches of 100 per page +- Continues fetching until all files are retrieved +- Logs a note when pagination occurs, showing the total file count +- **No file limit** - all changed files will be processed, even in very large PRs + +This ensures that critical workflows (such as merge conflict checking, link validation, etc.) don't miss files due to pagination limits. + +## Related Actions + +- **markdownlinks**: Uses this pattern to get changed markdown files +- **merge-conflict-checker**: Uses this pattern to get changed files for conflict detection +- **path-filters**: Similar functionality but with more complex filtering logic diff --git a/.github/actions/infrastructure/get-changed-files/action.yml b/.github/actions/infrastructure/get-changed-files/action.yml new file mode 100644 index 00000000000..51631cfe141 --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/action.yml @@ -0,0 +1,117 @@ +name: 'Get Changed Files' +description: 'Gets the list of files changed in a pull request or push event' +inputs: + filter: + description: 'Optional filter pattern (e.g., "*.md" for markdown files, ".github/" for GitHub files)' + required: false + default: '' + event-types: + description: 'Comma-separated list of event types to support (pull_request, push)' + required: false + default: 'pull_request' +outputs: + files: + description: 'JSON array of changed file paths' + value: ${{ steps.get-files.outputs.files }} + count: + description: 'Number of changed files' + value: ${{ steps.get-files.outputs.count }} +runs: + using: 'composite' + steps: + - name: Get changed files + id: get-files + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const eventTypes = '${{ inputs.event-types }}'.split(',').map(t => t.trim()); + const filter = '${{ inputs.filter }}'; + let changedFiles = []; + + if (eventTypes.includes('pull_request') && context.eventName === 'pull_request') { + console.log(`Getting files changed in PR #${context.payload.pull_request.number}`); + + // Fetch all files changed in the PR with pagination + let allFiles = []; + let page = 1; + let fetchedCount; + + do { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100, + page: page + }); + + allFiles = allFiles.concat(files); + fetchedCount = files.length; + page++; + } while (fetchedCount === 100); + + if (allFiles.length >= 100) { + console.log(`Note: This PR has ${allFiles.length} changed files. All files fetched using pagination.`); + } + + changedFiles = allFiles + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else if (eventTypes.includes('push') && context.eventName === 'push') { + console.log(`Getting files changed in push to ${context.ref}`); + + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: context.payload.before, + head: context.payload.after, + }); + + changedFiles = comparison.files + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else { + core.setFailed(`Unsupported event type: ${context.eventName}. Supported types: ${eventTypes.join(', ')}`); + return; + } + + // Apply filter if provided + if (filter) { + const filterLower = filter.toLowerCase(); + const beforeFilter = changedFiles.length; + changedFiles = changedFiles.filter(file => { + const fileLower = file.toLowerCase(); + // Support simple patterns like "*.md" or ".github/" + if (filterLower.startsWith('*.')) { + const ext = filterLower.substring(1); + return fileLower.endsWith(ext); + } else { + return fileLower.startsWith(filterLower); + } + }); + console.log(`Filter '${filter}' applied: ${beforeFilter} → ${changedFiles.length} files`); + } + + // Calculate simple hash for verification + const crypto = require('crypto'); + const filesJson = JSON.stringify(changedFiles.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + + // Log changed files in a collapsible group + core.startGroup(`Changed Files (${changedFiles.length} total, hash: ${hash})`); + if (changedFiles.length > 0) { + changedFiles.forEach(file => console.log(` - ${file}`)); + } else { + console.log(' (no files changed)'); + } + core.endGroup(); + + console.log(`Found ${changedFiles.length} changed files`); + core.setOutput('files', JSON.stringify(changedFiles)); + core.setOutput('count', changedFiles.length); + +branding: + icon: 'file-text' + color: 'blue' diff --git a/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 new file mode 100644 index 00000000000..a56d696eb6e --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 @@ -0,0 +1,182 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#requires -version 7 +# Markdig is always available in PowerShell 7 +<# +.SYNOPSIS + Parse CHANGELOG files using Markdig to extract links. + +.DESCRIPTION + This script uses Markdig.Markdown.Parse to parse all markdown files in the CHANGELOG directory + and extract different types of links (inline links, reference links, etc.). + +.PARAMETER ChangelogPath + Path to the CHANGELOG directory. Defaults to ./CHANGELOG + +.PARAMETER LinkType + Filter by link type: All, Inline, Reference, AutoLink. Defaults to All. + +.EXAMPLE + .\Parse-MarkdownLink.ps1 + +.EXAMPLE + .\Parse-MarkdownLink.ps1 -LinkType Reference +#> + +param( + [string]$ChangelogPath = "./CHANGELOG", + [ValidateSet("All", "Inline", "Reference", "AutoLink")] + [string]$LinkType = "All" +) + +Write-Verbose "Using built-in Markdig functionality to parse markdown files" + +function Get-LinksFromMarkdownAst { + param( + [Parameter(Mandatory)] + [object]$Node, + [Parameter(Mandatory)] + [string]$FileName, + [System.Collections.ArrayList]$Links + ) + + if ($null -eq $Links) { + return + } + + # Check if current node is a link + if ($Node -is [Markdig.Syntax.Inlines.LinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 # Convert to 1-based line numbering + Column = $Node.Column + 1 # Convert to 1-based column numbering + Url = $Node.Url ?? "" + Text = $Node.FirstChild?.ToString() ?? "" + Type = "Inline" + IsImage = $Node.IsImage + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.Inlines.AutolinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Url ?? "" + Type = "AutoLink" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinitionGroup]) { + foreach ($refDef in $Node) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $refDef.Line + 1 + Column = $refDef.Column + 1 + Url = $refDef.Url ?? "" + Text = $refDef.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinition]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + + # For MarkdownDocument (root), iterate through all blocks + if ($Node -is [Markdig.Syntax.MarkdownDocument]) { + foreach ($block in $Node) { + Get-LinksFromMarkdownAst -Node $block -FileName $FileName -Links $Links + } + } + # For block containers, iterate through children + elseif ($Node -is [Markdig.Syntax.ContainerBlock]) { + foreach ($child in $Node) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + } + } + # For leaf blocks with inlines, process the inline content + elseif ($Node -is [Markdig.Syntax.LeafBlock] -and $Node.Inline) { + Get-LinksFromMarkdownAst -Node $Node.Inline -FileName $FileName -Links $Links + } + # For inline containers, process all child inlines + elseif ($Node -is [Markdig.Syntax.Inlines.ContainerInline]) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } + # For other inline elements that might have children + elseif ($Node.PSObject.Properties.Name -contains "FirstChild" -and $Node.FirstChild) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } +} + +function Parse-ChangelogFiles { + param( + [string]$Path + ) + + if (-not (Test-Path $Path)) { + Write-Error "CHANGELOG directory not found: $Path" + return + } + + $markdownFiles = Get-ChildItem -Path $Path -Filter "*.md" -File + + if ($markdownFiles.Count -eq 0) { + Write-Warning "No markdown files found in $Path" + return + } + + $allLinks = [System.Collections.ArrayList]::new() + + foreach ($file in $markdownFiles) { + Write-Verbose "Processing file: $($file.Name)" + + try { + $content = Get-Content -Path $file.FullName -Raw -Encoding UTF8 + + # Parse the markdown content using Markdig + $document = [Markdig.Markdown]::Parse($content, [Markdig.MarkdownPipelineBuilder]::new()) + + # Extract links from the AST + Get-LinksFromMarkdownAst -Node $document -FileName $file.FullName -Links $allLinks + + } catch { + Write-Warning "Error processing file $($file.Name): $($_.Exception.Message)" + } + } + + # Filter by link type if specified + if ($LinkType -ne "All") { + $allLinks = $allLinks | Where-Object { $_.Type -eq $LinkType } + } + + return $allLinks +} + +# Main execution +$links = Parse-ChangelogFiles -Path $ChangelogPath + +# Output PowerShell objects +$links diff --git a/.github/actions/infrastructure/markdownlinks/README.md b/.github/actions/infrastructure/markdownlinks/README.md new file mode 100644 index 00000000000..e566ec2bcc3 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/README.md @@ -0,0 +1,177 @@ +# Verify Markdown Links Action + +A GitHub composite action that verifies all links in markdown files using PowerShell and Markdig. + +## Features + +- ✅ Parses markdown files using Markdig (built into PowerShell 7) +- ✅ Extracts all link types: inline links, reference links, and autolinks +- ✅ Verifies HTTP/HTTPS links with configurable timeouts and retries +- ✅ Validates local file references +- ✅ Supports excluding specific URL patterns +- ✅ Provides detailed error reporting with file locations +- ✅ Outputs metrics for CI/CD integration + +## Usage + +### Basic Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' +``` + +### Advanced Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'true' + timeout: 30 + max-retries: 2 + exclude-patterns: '*.example.com/*,*://localhost/*' +``` + +### With Outputs + +```yaml +- name: Verify Markdown Links + id: verify-links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'false' + +- name: Display Results + run: | + echo "Total links: ${{ steps.verify-links.outputs.total-links }}" + echo "Passed: ${{ steps.verify-links.outputs.passed-links }}" + echo "Failed: ${{ steps.verify-links.outputs.failed-links }}" + echo "Skipped: ${{ steps.verify-links.outputs.skipped-links }}" +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `path` | Path to the directory containing markdown files to verify | No | `./CHANGELOG` | +| `exclude-patterns` | Comma-separated list of URL patterns to exclude from verification | No | `''` | +| `fail-on-error` | Whether to fail the action if any links are broken | No | `true` | +| `timeout` | Timeout in seconds for HTTP requests | No | `30` | +| `max-retries` | Maximum number of retries for failed requests | No | `2` | + +## Outputs + +| Output | Description | +|--------|-------------| +| `total-links` | Total number of unique links checked | +| `passed-links` | Number of links that passed verification | +| `failed-links` | Number of links that failed verification | +| `skipped-links` | Number of links that were skipped | + +## Excluded Link Types + +The action automatically skips the following link types: + +- **Anchor links** (`#section-name`) - Would require full markdown parsing +- **Email links** (`mailto:user@example.com`) - Cannot be verified without sending email + +## GitHub Workflow Test + +This section provides a workflow example and instructions for testing the link verification action. + +### Testing the Workflow + +To test that the workflow properly detects broken links: + +1. Make change to this file (e.g., this README.md file already contains one in the [Broken Link Test](#broken-link-test) section) +1. The workflow will run and should fail, reporting the broken link(s) +1. Revert your change to this file +1. Push again to verify the workflow passes + +### Example Workflow Configuration + +```yaml +name: Verify Links + +on: + push: + branches: [ main ] + paths: + - '**/*.md' + pull_request: + branches: [ main ] + paths: + - '**/*.md' + schedule: + # Run weekly to catch external link rot + - cron: '0 0 * * 0' + +jobs: + verify-links: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Verify CHANGELOG Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'true' + + - name: Verify Documentation Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'false' + exclude-patterns: '*.internal.example.com/*' +``` + +## How It Works + +1. **Parse Markdown**: Uses `Parse-MarkdownLink.ps1` to extract all links from markdown files using Markdig +2. **Deduplicate**: Groups links by URL to avoid checking the same link multiple times +3. **Verify Links**: + - HTTP/HTTPS links: Makes HEAD/GET requests with configurable timeout and retries + - Local file references: Checks if the file exists relative to the markdown file + - Excluded patterns: Skips links matching the exclude patterns +4. **Report Results**: Displays detailed results with file locations for failed links +5. **Set Outputs**: Provides metrics for downstream steps + +## Error Output Example + +``` +✗ FAILED: https://example.com/broken-link - HTTP 404 + Found in: /path/to/file.md:42:15 + Found in: /path/to/other.md:100:20 + +Link Verification Summary +============================================================ +Total URLs checked: 150 +Passed: 145 +Failed: 2 +Skipped: 3 + +Failed Links: + • https://example.com/broken-link + Error: HTTP 404 + Occurrences: 2 +``` + +## Requirements + +- PowerShell 7+ (includes Markdig) +- Runs on: `ubuntu-latest`, `windows-latest`, `macos-latest` + +## Broken Link Test + +- [Broken Link](https://github.com/PowerShell/PowerShell/wiki/NonExistentPage404) + +## License + +Same as the PowerShell repository. diff --git a/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 new file mode 100644 index 00000000000..f50ab1590b9 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 @@ -0,0 +1,317 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 7.0 + +<# +.SYNOPSIS + Verify all links in markdown files. + +.DESCRIPTION + This script parses markdown files to extract links and verifies their accessibility. + It supports HTTP/HTTPS links and local file references. + +.PARAMETER Path + Path to the directory containing markdown files. Defaults to current directory. + +.PARAMETER File + Array of specific markdown files to verify. If provided, Path parameter is ignored. + +.PARAMETER TimeoutSec + Timeout in seconds for HTTP requests. Defaults to 30. + +.PARAMETER MaximumRetryCount + Maximum number of retries for failed requests. Defaults to 2. + +.PARAMETER RetryIntervalSec + Interval in seconds between retry attempts. Defaults to 2. + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./CHANGELOG + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./docs -FailOnError + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -File @('CHANGELOG/7.5.md', 'README.md') +#> + +param( + [Parameter(ParameterSetName = 'ByPath', Mandatory)] + [string]$Path = "Q:\src\git\powershell\docs\git", + [Parameter(ParameterSetName = 'ByFile', Mandatory)] + [string[]]$File = @(), + [int]$TimeoutSec = 30, + [int]$MaximumRetryCount = 2, + [int]$RetryIntervalSec = 2 +) + +$ErrorActionPreference = 'Stop' + +# Get the script directory +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Determine what to process: specific files or directory +if ($File.Count -gt 0) { + Write-Host "Extracting links from $($File.Count) specified markdown file(s)" -ForegroundColor Cyan + + # Process each file individually + $allLinks = @() + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + + foreach ($filePath in $File) { + if (Test-Path $filePath) { + Write-Verbose "Processing: $filePath" + $fileLinks = & $parseScriptPath -ChangelogPath $filePath + $allLinks += $fileLinks + } + else { + Write-Warning "File not found: $filePath" + } + } +} +else { + Write-Host "Extracting links from markdown files in: $Path" -ForegroundColor Cyan + + # Get all links from markdown files using the Parse-ChangelogLinks script + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + $allLinks = & $parseScriptPath -ChangelogPath $Path +} + +if ($allLinks.Count -eq 0) { + Write-Host "No links found in markdown files." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Found $($allLinks.Count) links to verify" -ForegroundColor Green + +# Group links by URL to avoid duplicate checks +$uniqueLinks = $allLinks | Group-Object -Property Url + +Write-Host "Unique URLs to verify: $($uniqueLinks.Count)" -ForegroundColor Cyan + +$results = @{ + Total = $uniqueLinks.Count + Passed = 0 + Failed = 0 + Skipped = 0 + Errors = [System.Collections.ArrayList]::new() +} + +function Test-HttpLink { + param( + [string]$Url + ) + + try { + # Try HEAD request first (faster, doesn't download content) + $response = Invoke-WebRequest -Uri $Url ` + -Method Head ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com/PowerShell/PowerShell)" ` + -SkipHttpErrorCheck + + # If HEAD fails with 404 or 405, retry with GET (some servers don't support HEAD) + if ($response.StatusCode -eq 404 -or $response.StatusCode -eq 405) { + Write-Verbose "HEAD request failed with $($response.StatusCode), retrying with GET for: $Url" + $response = Invoke-WebRequest -Uri $Url ` + -Method Get ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com)" ` + -SkipHttpErrorCheck + } + + if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) { + return @{ Success = $true; StatusCode = $response.StatusCode } + } + else { + return @{ Success = $false; StatusCode = $response.StatusCode; Error = "HTTP $($response.StatusCode)" } + } + } + catch { + return @{ Success = $false; StatusCode = 0; Error = $_.Exception.Message } + } +} + +function Test-LocalLink { + param( + [string]$Url, + [string]$BasePath + ) + + # Strip query parameters (e.g., ?sanitize=true) and anchors (e.g., #section) + $cleanUrl = $Url -replace '\?.*$', '' -replace '#.*$', '' + + # Handle relative paths + $targetPath = Join-Path $BasePath $cleanUrl + + if (Test-Path $targetPath) { + return @{ Success = $true } + } + else { + return @{ Success = $false; Error = "File not found: $targetPath" } + } +} + +# Verify each unique link +$progressCount = 0 +foreach ($linkGroup in $uniqueLinks) { + $progressCount++ + $url = $linkGroup.Name + $occurrences = $linkGroup.Group + Write-Verbose -Verbose "[$progressCount/$($uniqueLinks.Count)] Checking: $url" + + # Determine link type and verify + $verifyResult = $null + if ($url -match '^https?://') { + $verifyResult = Test-HttpLink -Url $url + } + elseif ($url -match '^#') { + Write-Verbose -Verbose "Skipping anchor link: $url" + $results.Skipped++ + continue + } + elseif ($url -match '^mailto:') { + Write-Verbose -Verbose "Skipping mailto link: $url" + $results.Skipped++ + continue + } + else { + $basePath = Split-Path -Parent $occurrences[0].Path + $verifyResult = Test-LocalLink -Url $url -BasePath $basePath + } + if ($verifyResult.Success) { + Write-Host "✓ OK: $url" -ForegroundColor Green + $results.Passed++ + } + else { + $errorMsg = if ($verifyResult.StatusCode) { + "HTTP $($verifyResult.StatusCode)" + } + else { + $verifyResult.Error + } + + # Determine if this status code should be ignored or treated as failure + # Ignore: 401 (Unauthorized), 403 (Forbidden), 429 (Too Many Requests - already retried) + # Fail: 404 (Not Found), 410 (Gone), 406 (Not Acceptable) - these indicate broken links + $shouldIgnore = $false + $ignoreReason = "" + + switch ($verifyResult.StatusCode) { + 401 { + $shouldIgnore = $true + $ignoreReason = "authentication required" + } + 403 { + $shouldIgnore = $true + $ignoreReason = "access forbidden" + } + 429 { + $shouldIgnore = $true + $ignoreReason = "rate limited (already retried)" + } + } + + if ($shouldIgnore) { + Write-Host "⊘ IGNORED: $url - $errorMsg ($ignoreReason)" -ForegroundColor Yellow + Write-Verbose -Verbose "Ignored error details for $url - Status: $($verifyResult.StatusCode) - $ignoreReason" + foreach ($occurrence in $occurrences) { + Write-Verbose -Verbose " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" + } + $results.Skipped++ + } + else { + Write-Host "✗ FAILED: $url - $errorMsg" -ForegroundColor Red + foreach ($occurrence in $occurrences) { + Write-Host " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" -ForegroundColor DarkGray + } + $results.Failed++ + [void]$results.Errors.Add(@{ + Url = $url + Error = $errorMsg + Occurrences = $occurrences + }) + } + } + } + +# Print summary +Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan +Write-Host "Link Verification Summary" -ForegroundColor Cyan +Write-Host ("=" * 60) -ForegroundColor Cyan +Write-Host "Total URLs checked: $($results.Total)" -ForegroundColor White +Write-Host "Passed: $($results.Passed)" -ForegroundColor Green +Write-Host "Failed: $($results.Failed)" -ForegroundColor $(if ($results.Failed -gt 0) { "Red" } else { "Green" }) +Write-Host "Skipped: $($results.Skipped)" -ForegroundColor Gray + +if ($results.Failed -gt 0) { + Write-Host "`nFailed Links:" -ForegroundColor Red + foreach ($failedLink in $results.Errors) { + Write-Host " • $($failedLink.Url)" -ForegroundColor Red + Write-Host " Error: $($failedLink.Error)" -ForegroundColor DarkGray + Write-Host " Occurrences: $($failedLink.Occurrences.Count)" -ForegroundColor DarkGray + } + + Write-Host "`n❌ Link verification failed!" -ForegroundColor Red + exit 1 +} +else { + Write-Host "`n✅ All links verified successfully!" -ForegroundColor Green +} + +# Write to GitHub Actions step summary if running in a workflow +if ($env:GITHUB_STEP_SUMMARY) { + $summaryContent = @" + +# Markdown Link Verification Results + +## Summary +- **Total URLs checked:** $($results.Total) +- **Passed:** ✅ $($results.Passed) +- **Failed:** $(if ($results.Failed -gt 0) { "❌" } else { "✅" }) $($results.Failed) +- **Skipped:** $($results.Skipped) + +"@ + + if ($results.Failed -gt 0) { + $summaryContent += @" + +## Failed Links + +| URL | Error | Occurrences | +|-----|-------|-------------| + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "| $($failedLink.Url) | $($failedLink.Error) | $($failedLink.Occurrences.Count) |`n" + } + + $summaryContent += @" + +
+Click to see all failed link locations + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "`n### $($failedLink.Url)`n" + $summaryContent += "**Error:** $($failedLink.Error)`n`n" + foreach ($occurrence in $failedLink.Occurrences) { + $summaryContent += "- `$($occurrence.Path):$($occurrence.Line):$($occurrence.Column)`n" + } + } + $summaryContent += "`n
`n" + } + else { + $summaryContent += "`n## ✅ All links verified successfully!`n" + } + + Write-Verbose -Verbose "Writing `n $summaryContent `n to ${env:GITHUB_STEP_SUMMARY}" + $summaryContent | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append + Write-Verbose -Verbose "Summary written to GitHub Actions step summary" +} + diff --git a/.github/actions/infrastructure/markdownlinks/action.yml b/.github/actions/infrastructure/markdownlinks/action.yml new file mode 100644 index 00000000000..de2952252d4 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/action.yml @@ -0,0 +1,110 @@ +name: 'Verify Markdown Links' +description: 'Verify all links in markdown files using PowerShell and Markdig' +author: 'PowerShell Team' + +inputs: + timeout-sec: + description: 'Timeout in seconds for HTTP requests' + required: false + default: '30' + maximum-retry-count: + description: 'Maximum number of retries for failed requests' + required: false + default: '2' + +outputs: + total-links: + description: 'Total number of unique links checked' + value: ${{ steps.verify.outputs.total }} + passed-links: + description: 'Number of links that passed verification' + value: ${{ steps.verify.outputs.passed }} + failed-links: + description: 'Number of links that failed verification' + value: ${{ steps.verify.outputs.failed }} + skipped-links: + description: 'Number of links that were skipped' + value: ${{ steps.verify.outputs.skipped }} + +runs: + using: 'composite' + steps: + - name: Get changed markdown files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '*.md' + event-types: 'pull_request,push' + + - name: Verify markdown links + id: verify + shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} + run: | + Write-Host "Starting markdown link verification..." -ForegroundColor Cyan + + # Get changed markdown files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON + $changedFiles = $changedFilesJson | ConvertFrom-Json + + if ($changedFiles.Count -eq 0) { + Write-Host "No markdown files changed, skipping verification" -ForegroundColor Yellow + "total=0" >> $env:GITHUB_OUTPUT + "passed=0" >> $env:GITHUB_OUTPUT + "failed=0" >> $env:GITHUB_OUTPUT + "skipped=0" >> $env:GITHUB_OUTPUT + exit 0 + } + + Write-Host "Changed markdown files: $($changedFiles.Count)" -ForegroundColor Cyan + $changedFiles | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray } + + # Build parameters for each file + $params = @{ + File = $changedFiles + TimeoutSec = [int]'${{ inputs.timeout-sec }}' + MaximumRetryCount = [int]'${{ inputs.maximum-retry-count }}' + } + + # Run the verification script + $scriptPath = Join-Path '${{ github.action_path }}' 'Verify-MarkdownLinks.ps1' + + # Capture output and parse results + $output = & $scriptPath @params 2>&1 | Tee-Object -Variable capturedOutput + + # Try to extract metrics from output + $totalLinks = 0 + $passedLinks = 0 + $failedLinks = 0 + $skippedLinks = 0 + + foreach ($line in $capturedOutput) { + if ($line -match 'Total URLs checked: (\d+)') { + $totalLinks = $Matches[1] + } + elseif ($line -match 'Passed: (\d+)') { + $passedLinks = $Matches[1] + } + elseif ($line -match 'Failed: (\d+)') { + $failedLinks = $Matches[1] + } + elseif ($line -match 'Skipped: (\d+)') { + $skippedLinks = $Matches[1] + } + } + + # Set outputs + "total=$totalLinks" >> $env:GITHUB_OUTPUT + "passed=$passedLinks" >> $env:GITHUB_OUTPUT + "failed=$failedLinks" >> $env:GITHUB_OUTPUT + "skipped=$skippedLinks" >> $env:GITHUB_OUTPUT + + Write-Host "Action completed" -ForegroundColor Cyan + + # Exit with the same code as the verification script + exit $LASTEXITCODE + +branding: + icon: 'link' + color: 'blue' diff --git a/.github/actions/infrastructure/merge-conflict-checker/README.md b/.github/actions/infrastructure/merge-conflict-checker/README.md new file mode 100644 index 00000000000..b53d6f99964 --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/README.md @@ -0,0 +1,86 @@ +# Merge Conflict Checker + +This composite GitHub Action checks for Git merge conflict markers in files changed in pull requests. + +## Purpose + +Automatically detects leftover merge conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in pull request files to prevent them from being merged into the codebase. + +## Usage + +### In a Workflow + +```yaml +- name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +### Complete Example + +```yaml +jobs: + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@v5 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +## How It Works + +1. **File Detection**: Uses GitHub's API to get the list of files changed in the pull request +2. **Marker Scanning**: Reads each changed file and searches for the following markers: + - `<<<<<<<` (conflict start marker) + - `=======` (conflict separator) + - `>>>>>>>` (conflict end marker) +3. **Result Reporting**: + - If markers are found, the action fails and lists all affected files + - If no markers are found, the action succeeds + +## Outputs + +- `files-checked`: Number of files that were checked +- `conflicts-found`: Number of files containing merge conflict markers + +## Behavior + +- **Event Support**: Only works with `pull_request` events +- **File Handling**: + - Checks only files that were added, modified, or renamed + - Skips deleted files + - **Filters out `*.cs` files** (C# files are excluded from merge conflict checking) + - Skips binary/unreadable files + - Skips directories +- **Empty File List**: Gracefully handles cases where no files need checking (e.g., PRs that only delete files) + +## Example Output + +When conflict markers are detected: + +``` +❌ Merge conflict markers detected in the following files: + - src/example.cs + Markers found: <<<<<<<, =======, >>>>>>> + - README.md + Markers found: <<<<<<<, =======, >>>>>>> + +Please resolve these conflicts before merging. +``` + +When no markers are found: + +``` +✅ No merge conflict markers found +``` + +## Integration + +This action is integrated into the `linux-ci.yml` workflow and runs automatically on all pull requests to ensure code quality before merging. diff --git a/.github/actions/infrastructure/merge-conflict-checker/action.yml b/.github/actions/infrastructure/merge-conflict-checker/action.yml new file mode 100644 index 00000000000..41c7d2ad941 --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/action.yml @@ -0,0 +1,37 @@ +name: 'Check for Merge Conflict Markers' +description: 'Checks for Git merge conflict markers in changed files for pull requests' +author: 'PowerShell Team' + +outputs: + files-checked: + description: 'Number of files checked for merge conflict markers' + value: ${{ steps.check.outputs.files-checked }} + conflicts-found: + description: 'Number of files with merge conflict markers' + value: ${{ steps.check.outputs.conflicts-found }} + +runs: + using: 'composite' + steps: + - name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + + - name: Check for merge conflict markers + id: check + shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} + run: | + # Get changed files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON + # Ensure we always have an array (ConvertFrom-Json returns null for empty JSON arrays) + $changedFiles = @($changedFilesJson | ConvertFrom-Json) + + # Import ci.psm1 and run the check + Import-Module "$env:GITHUB_WORKSPACE/tools/ci.psm1" -Force + Test-MergeConflictMarker -File $changedFiles -WorkspacePath $env:GITHUB_WORKSPACE + +branding: + icon: 'alert-triangle' + color: 'red' diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml new file mode 100644 index 00000000000..af23540256d --- /dev/null +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -0,0 +1,137 @@ +name: Path Filters +description: 'Path Filters' +inputs: + GITHUB_TOKEN: + description: 'GitHub token' + required: true +outputs: + source: + description: 'Source code changes (composite of all changes)' + value: ${{ steps.filter.outputs.source }} + githubChanged: + description: 'GitHub workflow changes' + value: ${{ steps.filter.outputs.githubChanged }} + toolsChanged: + description: 'Tools changes' + value: ${{ steps.filter.outputs.toolsChanged }} + propsChanged: + description: 'Props changes' + value: ${{ steps.filter.outputs.propsChanged }} + testsChanged: + description: 'Tests changes' + value: ${{ steps.filter.outputs.testsChanged }} + mainSourceChanged: + description: 'Main source code changes (any changes in src/)' + value: ${{ steps.filter.outputs.mainSourceChanged }} + buildModuleChanged: + description: 'Build module changes' + value: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: + description: 'Packaging related changes' + value: ${{ steps.filter.outputs.packagingChanged }} +runs: + using: composite + steps: + - name: Get changed files + id: get-files + if: github.event_name == 'pull_request' + uses: "./.github/actions/infrastructure/get-changed-files" + + - name: Check if GitHubWorkflowChanges is present + id: filter + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + FILES_JSON: ${{ steps.get-files.outputs.files }} + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + script: | + console.log(`Event Name: ${context.eventName}`); + + // Just say everything changed if this is not a PR + if (context.eventName !== 'pull_request') { + console.log('Not a pull request, setting all outputs to true'); + core.setOutput('toolsChanged', true); + core.setOutput('githubChanged', true); + core.setOutput('propsChanged', true); + core.setOutput('testsChanged', true); + core.setOutput('mainSourceChanged', true); + core.setOutput('buildModuleChanged', true); + core.setOutput('source', true); + return; + } + + // Get files from environment variable (secure against injection) + const files = JSON.parse(process.env.FILES_JSON || '[]'); + + // Calculate hash for verification (matches get-changed-files action) + const crypto = require('crypto'); + const filesJson = JSON.stringify(files.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + console.log(`Received ${files.length} files (hash: ${hash})`); + + // Analyze changes with detailed logging + core.startGroup('Path Filter Analysis'); + + const actionsChanged = files.some(file => file.startsWith('.github/actions')); + console.log(`✓ Actions changed: ${actionsChanged}`); + + const workflowsChanged = files.some(file => file.startsWith('.github/workflows')); + console.log(`✓ Workflows changed: ${workflowsChanged}`); + + const githubChanged = actionsChanged || workflowsChanged; + console.log(`→ GitHub changed (actions OR workflows): ${githubChanged}`); + + const toolsCiPsm1Changed = files.some(file => file === 'tools/ci.psm1'); + console.log(`✓ tools/ci.psm1 changed: ${toolsCiPsm1Changed}`); + + const toolsBuildCommonChanged = files.some(file => file.startsWith('tools/buildCommon/')); + console.log(`✓ tools/buildCommon/ changed: ${toolsBuildCommonChanged}`); + + const toolsChanged = toolsCiPsm1Changed || toolsBuildCommonChanged; + console.log(`→ Tools changed: ${toolsChanged}`); + + const propsChanged = files.some(file => file.endsWith('.props')); + console.log(`✓ Props files changed: ${propsChanged}`); + + const testsChanged = files.some(file => file.startsWith('test/powershell/') || file.startsWith('test/tools/') || file.startsWith('test/xUnit/')); + console.log(`✓ Tests changed: ${testsChanged}`); + + const mainSourceChanged = files.some(file => file.startsWith('src/')); + console.log(`✓ Main source (src/) changed: ${mainSourceChanged}`); + + const buildModuleChanged = files.some(file => file === 'build.psm1'); + console.log(`✓ build.psm1 changed: ${buildModuleChanged}`); + + const globalConfigChanged = files.some(file => file === '.globalconfig' || file === 'nuget.config' || file === 'global.json'); + console.log(`✓ Global config changed: ${globalConfigChanged}`); + + const packagingChanged = files.some(file => + file === '.github/workflows/windows-ci.yml' || + file === '.github/workflows/linux-ci.yml' || + file.startsWith('assets/wix/') || + file === 'PowerShell.Common.props' || + file.match(/^src\/.*\.csproj$/) || + file.startsWith('test/packaging/windows/') || + file.startsWith('test/packaging/linux/') || + file.startsWith('tools/packaging/') || + file.startsWith('tools/wix/') + ) || + buildModuleChanged || + globalConfigChanged || + toolsCiPsm1Changed; + console.log(`→ Packaging changed: ${packagingChanged}`); + + const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged || globalConfigChanged; + console.log(`→ Source (composite): ${source}`); + + core.endGroup(); + + core.setOutput('toolsChanged', toolsChanged); + core.setOutput('githubChanged', githubChanged); + core.setOutput('propsChanged', propsChanged); + core.setOutput('testsChanged', testsChanged); + core.setOutput('mainSourceChanged', mainSourceChanged); + core.setOutput('buildModuleChanged', buildModuleChanged); + core.setOutput('globalConfigChanged', globalConfigChanged); + core.setOutput('packagingChanged', packagingChanged); + core.setOutput('source', source); diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml new file mode 100644 index 00000000000..ce37a38c8b7 --- /dev/null +++ b/.github/actions/test/linux-packaging/action.yml @@ -0,0 +1,69 @@ +name: linux_packaging +description: 'Linux packaging for PowerShell' + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + - name: Bootstrap + run: |- + Import-Module ./build.psm1 + Start-PSBootstrap -Scenario Package + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + shell: pwsh + + - name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh + + - name: Install Pester + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + + - name: Validate Package Names + run: |- + # Run Pester tests to validate package names + Import-Module Pester -Force + $testResults = Invoke-Pester -Path ./test/packaging/linux/package-validation.tests.ps1 -PassThru + if ($testResults.FailedCount -gt 0) { + throw "Package validation tests failed" + } + shell: pwsh + + - name: Upload deb packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-deb + path: ${{ runner.workspace }}/packages/*.deb + if-no-files-found: ignore + + - name: Upload rpm packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-rpm + path: ${{ runner.workspace }}/packages/*.rpm + if-no-files-found: ignore + + - name: Upload tar.gz packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-tar + path: ${{ runner.workspace }}/packages/*.tar.gz + if-no-files-found: ignore diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml new file mode 100644 index 00000000000..ab30e0d9ce6 --- /dev/null +++ b/.github/actions/test/nix/action.yml @@ -0,0 +1,158 @@ +name: nix_test +description: 'Test PowerShell on non-Windows platforms' + +inputs: + purpose: + required: false + default: '' + type: string + tagSet: + required: false + default: CI + type: string + ctrfFolder: + required: false + default: ctrf + type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: "${{ github.workspace }}" + + - name: Capture Artifacts Directory + continue-on-error: true + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Artifacts Directory' + Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + Write-LogGroupEnd -Title 'Artifacts Directory' + shell: pwsh + + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + global-json-file: ./global.json + + - name: Set Package Name by Platform + id: set_package_name + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $platform = $env:RUNNER_OS + Write-Host "Runner platform: $platform" + if ($platform -eq 'Linux') { + $packageName = 'DSC-*-x86_64-linux.tar.gz' + } elseif ($platform -eq 'macOS') { + $packageName = 'DSC-*-x86_64-apple-darwin.tar.gz' + } else { + throw "Unsupported platform: $platform" + } + + Set-GWVariable -Name "DSC_PACKAGE_NAME" -Value $packageName + + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $packageName = "$env:DSC_PACKAGE_NAME" + + Write-Host "Package Name: $packageName" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "*$packageName*" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + + $tempPath = Get-GWTempPath + + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose -Headers $headers + New-Item -ItemType Directory -Path "$tempPath/DSC" -Force -Verbose + tar xvf "$tempPath/DSC.tar.gz" -C "$tempPath/DSC" + $dscRoot = "$tempPath/DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + + - name: Bootstrap + shell: pwsh + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Write-LogGroupEnd -Title 'Bootstrap' + + - name: Extract Files + uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0 + env: + DESTINATION_FOLDER: "${{ github.workspace }}/bins" + ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" + with: + script: |- + const fs = require('fs').promises + const path = require('path') + const target = path.resolve(process.env.DESTINATION_FOLDER) + const patterns = process.env.ARCHIVE_FILE_PATTERNS + const globber = await glob.create(patterns) + await io.mkdirP(path.dirname(target)) + for await (const file of globber.globGenerator()) { + if ((await fs.lstat(file)).isDirectory()) continue + await exec.exec(`7z x ${file} -o${target} -aoa`) + } + + - name: Fix permissions + continue-on-error: true + run: |- + find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; + find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; + shell: bash + + - name: Capture Extracted Build ZIP + continue-on-error: true + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Extracted Build ZIP' + Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + Write-LogGroupEnd -Title 'Extracted Build ZIP' + shell: pwsh + + - name: Test + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath '${{ github.workspace }}/build/psoptions.json' + $options = (Get-PSOptions) + $rootPath = '${{ github.workspace }}/bins' + $originalRootPath = Split-Path -path $options.Output + $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) + $pwshPath = Join-Path -path $path -ChildPath 'pwsh' + chmod a+x $pwshPath + $options.Output = $pwshPath + Set-PSOptions $options + Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -TitlePrefix '${{ inputs.buildName }}' -OutputFormat NUnitXml + shell: pwsh + + - name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: "${{ runner.workspace }}/testResults" + ctrfFolder: "${{ inputs.ctrfFolder }}" diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml new file mode 100644 index 00000000000..44f2037626f --- /dev/null +++ b/.github/actions/test/process-pester-results/action.yml @@ -0,0 +1,27 @@ +name: process-pester-test-results +description: 'Process Pester test results' + +inputs: + name: + required: true + default: '' + type: string + testResultsFolder: + required: false + default: "${{ runner.workspace }}/testResults" + type: string + +runs: + using: composite + steps: + - name: Log Summary + run: |- + & "$env:GITHUB_ACTION_PATH/process-pester-results.ps1" -Name '${{ inputs.name }}' -TestResultsFolder '${{ inputs.testResultsFolder }}' + shell: pwsh + + - name: Upload testResults artifact + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: junit-pester-${{ inputs.name }} + path: ${{ runner.workspace }}/testResults diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 new file mode 100644 index 00000000000..5804bec9a94 --- /dev/null +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -0,0 +1,124 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param( + [parameter(Mandatory)] + [string]$Name, + [parameter(Mandatory)] + [string]$TestResultsFolder +) + +Import-Module "$PSScriptRoot/../../../../build.psm1" + +if (-not $env:GITHUB_STEP_SUMMARY) { + Write-Error "GITHUB_STEP_SUMMARY is not set. Ensure this workflow is running in a GitHub Actions environment." + exit 1 +} + +$testCaseCount = 0 +$testErrorCount = 0 +$testFailureCount = 0 +$testNotRunCount = 0 +$testInconclusiveCount = 0 +$testIgnoredCount = 0 +$testSkippedCount = 0 +$testInvalidCount = 0 + +# Process test results and generate annotations for failures +Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { + $results = [xml] (get-content $_.FullName) + + $testCaseCount += [int]$results.'test-results'.total + $testErrorCount += [int]$results.'test-results'.errors + $testFailureCount += [int]$results.'test-results'.failures + $testNotRunCount += [int]$results.'test-results'.'not-run' + $testInconclusiveCount += [int]$results.'test-results'.inconclusive + $testIgnoredCount += [int]$results.'test-results'.ignored + $testSkippedCount += [int]$results.'test-results'.skipped + $testInvalidCount += [int]$results.'test-results'.invalid + + # Generate GitHub Actions annotations for test failures + # Select failed test cases + if ("System.Xml.XmlDocumentXPathExtensions" -as [Type]) { + $failures = [System.Xml.XmlDocumentXPathExtensions]::SelectNodes($results.'test-results', './/test-case[@result = "Failure"]') + } + else { + $failures = $results.SelectNodes('.//test-case[@result = "Failure"]') + } + + foreach ($testfail in $failures) { + $description = $testfail.description + $testName = $testfail.name + $message = $testfail.failure.message + $stack_trace = $testfail.failure.'stack-trace' + + # Parse stack trace to get file and line info + $fileInfo = Get-PesterFailureFileInfo -StackTraceString $stack_trace + + if ($fileInfo.File) { + # Convert absolute path to relative path for GitHub Actions + $filePath = $fileInfo.File + + # GitHub Actions expects paths relative to the workspace root + if ($env:GITHUB_WORKSPACE) { + $workspacePath = $env:GITHUB_WORKSPACE + if ($filePath.StartsWith($workspacePath)) { + $filePath = $filePath.Substring($workspacePath.Length).TrimStart('/', '\') + # Normalize to forward slashes for consistency + $filePath = $filePath -replace '\\', '/' + } + } + + # Create annotation title + $annotationTitle = "Test Failure: $description / $testName" + + # Build the annotation message + $annotationMessage = $message -replace "`n", "%0A" -replace "`r" + + # Build and output the workflow command + $workflowCommand = "::error file=$filePath" + if ($fileInfo.Line) { + $workflowCommand += ",line=$($fileInfo.Line)" + } + $workflowCommand += ",title=$annotationTitle::$annotationMessage" + + Write-Host $workflowCommand + + # Output a link to the test run + if ($env:GITHUB_SERVER_URL -and $env:GITHUB_REPOSITORY -and $env:GITHUB_RUN_ID) { + $logUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)" + Write-Host "Test logs: $logUrl" + } + } + } +} + +@" + +# Summary of $Name + +- Total Tests: $testCaseCount +- Total Errors: $testErrorCount +- Total Failures: $testFailureCount +- Total Not Run: $testNotRunCount +- Total Inconclusive: $testInconclusiveCount +- Total Ignored: $testIgnoredCount +- Total Skipped: $testSkippedCount +- Total Invalid: $testInvalidCount + +"@ | Out-File -FilePath $ENV:GITHUB_STEP_SUMMARY -Append + +Write-Log "Summary written to $ENV:GITHUB_STEP_SUMMARY" + +Write-LogGroupStart -Title 'Test Results' +Get-Content $ENV:GITHUB_STEP_SUMMARY +Write-LogGroupEnd -Title 'Test Results' + +if ($testErrorCount -gt 0 -or $testFailureCount -gt 0) { + Write-Error "There were $testErrorCount/$testFailureCount errors/failures in the test results." + exit 1 +} +if ($testCaseCount -eq 0) { + Write-Error "No test cases were run." + exit 1 +} diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml new file mode 100644 index 00000000000..ddc5da4d664 --- /dev/null +++ b/.github/actions/test/windows/action.yml @@ -0,0 +1,107 @@ +name: windows_test +description: 'Test PowerShell on Windows' + +inputs: + purpose: + required: false + default: '' + type: string + tagSet: + required: false + default: CI + type: string + ctrfFolder: + required: false + default: ctrf + type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: "${{ github.workspace }}" + + - name: Capture Artifacts Directory + continue-on-error: true + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Artifacts Directory' + Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + Write-LogGroupEnd -Title 'Artifacts Directory' + shell: pwsh + + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + global-json-file: .\global.json + + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module .\.github\workflows\GHWorkflowHelper\GHWorkflowHelper.psm1 + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + $tempPath = Get-GWTempPath + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" -Headers $headers + + $null = New-Item -ItemType Directory -Path "$tempPath\DSC" -Force + Expand-Archive -Path "$tempPath\DSC.zip" -DestinationPath "$tempPath\DSC" -Force + $dscRoot = "$tempPath\DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + + - name: Bootstrap + shell: powershell + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' + Write-Host "Old Path:" + Write-Host $env:Path + $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' + $paths = $env:Path -split ";" | Where-Object { -not $_.StartsWith($dotnetPath) } + $env:Path = $paths -join ";" + Write-Host "New Path:" + Write-Host $env:Path + # Bootstrap + Import-Module .\tools\ci.psm1 + Invoke-CIInstall + Write-LogGroupEnd -Title 'Bootstrap' + + - name: Test + if: success() + run: |- + Import-Module .\build.psm1 -force + Import-Module .\tools\ci.psm1 + Restore-PSOptions -PSOptionsPath '${{ github.workspace }}\build\psoptions.json' + $options = (Get-PSOptions) + $path = split-path -path $options.Output + $rootPath = split-Path -path $path + Expand-Archive -Path '${{ github.workspace }}\build\build.zip' -DestinationPath $rootPath -Force + Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -OutputFormat NUnitXml + shell: pwsh + + - name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: ${{ runner.workspace }}\testResults + ctrfFolder: "${{ inputs.ctrfFolder }}" diff --git a/.github/agents/SplitADOPipelines.agent.md b/.github/agents/SplitADOPipelines.agent.md new file mode 100644 index 00000000000..9454670061f --- /dev/null +++ b/.github/agents/SplitADOPipelines.agent.md @@ -0,0 +1,180 @@ +--- +name: SplitADOPipelines +description: This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'todo'] +--- + +This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. + +A repository will have under the .pipelines directory a series of yaml files that define the ADO pipelines for the repository. + +First confirm if the pipelines are using a toggle switch for Official and NonOfficial. This will look something like this + +```yaml +parameters: + - name: templateFile + value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} +``` + +Followed by: + +```yaml +extends: + template: ${{ variables.templateFile }} +``` + +This is an indicator that this work needs to be done. This toggle switch is no longer allowed and the templates need to be hard coded. + +## Template Reference Convention (MUST follow) + +All `- template:` references to files **inside this repo** must use the **absolute** form anchored at the repo root, with the `@self` suffix: + +```yaml +- template: /.pipelines/templates//.yml@self +``` + +Do **not** use relative paths such as `templates/...`, `../templates/...`, or bare filenames. Rationale: + +- Absolute paths resolve identically regardless of where the referring file lives, so moving a pipeline file between directories (for example, into `.pipelines/NonOfficial/`) does not silently break includes. +- Relative paths are resolved by Azure DevOps against the directory of the referring file, which has caused real outages in this repo when a relative include was composed into a nonexistent nested path like `.pipelines/templates/stages/.pipelines/templates/...`. +- The majority of existing includes already use the absolute form; keeping new work consistent reduces review burden. + +The only acceptable non-absolute references are to external repositories resolved via the `resources.repositories` block, for example `v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates`. + +## Refactoring Steps + +### Step 1: Extract Shared Templates + +For each pipeline file that uses the toggle switch pattern (e.g., `PowerShell-Packages-Official.yml`): + +1. Create the `.pipelines/templates/variables` and `.pipelines/templates/stages` directories if they don't exist +2. Extract the **variables section** into `.pipelines/templates/variables/PowerShell-Packages-Variables.yml` +3. Extract the **stages section** into `.pipelines/templates/stages/PowerShell-Packages-Stages.yml` + +**IMPORTANT**: Only extract the `variables:` and `stages:` sections. All other sections (parameters, resources, extends, etc.) remain in the pipeline files. + +### Step 2: Create Official Pipeline (In-Place Refactoring) + +The original toggle-based file becomes the Official pipeline: + +1. **Keep the file in its original location** (e.g., `.pipelines/PowerShell-Packages-Official.yml` stays where it is) +2. Remove the toggle switch parameter (`templateFile` parameter) +3. Hard-code the Official template reference: + ```yaml + extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + ``` +4. Replace the `variables:` section with a template reference: + ```yaml + variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + ``` +5. Replace the `stages:` section with a template reference: + ```yaml + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + ``` + +### Step 3: Create NonOfficial Pipeline + +1. Create `.pipelines/NonOfficial` directory if it doesn't exist +2. Create the NonOfficial pipeline file (e.g., `.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`) +3. Copy the structure from the refactored Official pipeline +4. Hard-code the NonOfficial template reference: + ```yaml + extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + ``` +5. Reference the same shared templates: + ```yaml + variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + ``` + +**Note**: Always use **absolute** template paths of the form `/.pipelines/templates/...@self`. Do not use relative paths like `templates/...` or `../templates/...`. Absolute paths are anchored at the repo root and resolve consistently from any referring file, preventing breakage when files are moved between directories. + +### Step 4: Link NonOfficial Pipelines to NonOfficial Dependencies + +After creating NonOfficial pipelines, ensure they consume artifacts from other **NonOfficial** pipelines, not Official ones. + +1. **Check the `resources:` section** in each NonOfficial pipeline for `pipelines:` dependencies +2. **Identify Official pipeline references** that need to be changed to NonOfficial +3. **Update the `source:` field** to point to the NonOfficial version + +**Example Problem:** NonOfficial pipeline pointing to Official dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' # ❌ Wrong - Official! +``` + +**Solution:** Update to NonOfficial dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-NonOfficial' # ✅ Correct - NonOfficial! +``` + +**IMPORTANT**: The `source:` field must match the **exact ADO pipeline definition name** as it appears in Azure DevOps, not necessarily the file name. + +### Step 5: Configure Release Environment Parameters (NonAzure Only) + +**This step only applies if the pipeline uses `category: NonAzure` in the release configuration.** + +If you detect this pattern in the original pipeline: + +```yaml +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates # or NonOfficial + parameters: + release: + category: NonAzure +``` + +Then you must configure the `ob_release_environment` parameter when referencing the stages template. + +#### Official Pipeline Configuration + +In the Official pipeline (e.g., `.pipelines/PowerShell-Packages-Official.yml`): + +```yaml +stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + ob_release_environment: Production +``` + +#### NonOfficial Pipeline Configuration + +In the NonOfficial pipeline (e.g., `.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`): + +```yaml +stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + ob_release_environment: Test +``` + +#### Update Stages Template to Accept Parameter + +The extracted stages template (e.g., `.pipelines/templates/stages/PowerShell-Packages-Stages.yml`) must declare the parameter at the top: + +```yaml +parameters: + - name: ob_release_environment + type: string + +stages: + # ... rest of stages configuration using ${{ parameters.ob_release_environment }} +``` + +**IMPORTANT**: +- Only configure this for pipelines with `category: NonAzure` +- Official pipelines always use `ob_release_environment: Production` +- NonOfficial pipelines always use `ob_release_environment: Test` +- The stages template must accept this parameter and use it in the appropriate stage configurations diff --git a/.github/chatmodes/cherry-pick-commits.chatmode.md b/.github/chatmodes/cherry-pick-commits.chatmode.md new file mode 100644 index 00000000000..826ab11d56c --- /dev/null +++ b/.github/chatmodes/cherry-pick-commits.chatmode.md @@ -0,0 +1,78 @@ +# Cherry-Pick Commits Between Branches + +Cherry-pick recent commits from a source branch to a target branch without switching branches. + +## Instructions for Copilot + +1. **Confirm branches with the user** + - Ask the user to confirm the source and target branches + - If different branches are needed, update the configuration + +2. **Identify unique commits** + - Run: `git log .. --oneline --reverse` + - **IMPORTANT**: The commit count may be misleading if branches diverged from different base commits + - Compare the LAST few commits from each branch to identify actual missing commits: + - `git log --oneline -10` + - `git log --oneline -10` + - Look for commits with the same message but different SHAs (rebased commits) + - Show the user ONLY the truly missing commits (usually just the most recent ones) + +3. **Confirm with user before proceeding** + - If the commit count seems unusually high (e.g., 400+), STOP and verify semantically + - Ask: "I found X commits to cherry-pick. Shall I proceed?" + - If there are many commits, warn that this may take time + +4. **Execute the cherry-pick** + - Ensure the target branch is checked out first + - Run: `git cherry-pick ` for single commits + - Or: `git cherry-pick ` for multiple commits + - Apply commits in chronological order (oldest first) + +5. **Handle any issues** + - If conflicts occur, pause and ask user for guidance + - If empty commits occur, automatically skip with `git cherry-pick --skip` + +6. **Verify and report results** + - Run: `git log - --oneline` + - Show the user the newly applied commits + - Confirm the branch is now ahead by X commits + +## Key Git Commands + +```bash +# Find unique commits (may show full divergence if branches were rebased) +git log .. --oneline --reverse + +# Compare recent commits on each branch (more reliable for rebased branches) +git log --oneline -10 +git log --oneline -10 + +# Cherry-pick specific commits (when target is checked out) +git cherry-pick +git cherry-pick + +# Skip empty commits +git cherry-pick --skip + +# Verify result +git log - --oneline +``` + +## Common Scenarios + +- **Empty commits**: Automatically skip with `git cherry-pick --skip` +- **Conflicts**: Stop, show files with conflicts, ask user to resolve +- **Many commits**: Warn user and confirm before proceeding +- **Already applied**: These will result in empty commits that should be skipped +- **Diverged branches**: If branches diverged (rebased), `git log` may show the entire history difference + - The actual missing commits are usually only the most recent ones + - Compare commit messages from recent history on both branches + - Cherry-pick only commits that are semantically missing + +## Workflow Style + +Use an interactive, step-by-step approach: +- Show output from each command +- Ask for confirmation before major actions +- Provide clear status updates +- Handle errors gracefully with user guidance diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..45d2e8fe928 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,32 @@ +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + labels: + - "CL-BuildPackaging" + + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "release/*" + schedule: + interval: "daily" + labels: + - "CL-BuildPackaging" + + - package-ecosystem: "docker" + directory: / + schedule: + interval: daily + labels: + - "CL-BuildPackaging" + + - package-ecosystem: "docker" + directory: "/" + target-branch: "release/*" + schedule: + interval: daily + labels: + - "CL-BuildPackaging" diff --git a/.github/instructions/build-and-packaging-steps.instructions.md b/.github/instructions/build-and-packaging-steps.instructions.md new file mode 100644 index 00000000000..934b1539593 --- /dev/null +++ b/.github/instructions/build-and-packaging-steps.instructions.md @@ -0,0 +1,127 @@ +--- +applyTo: + - ".github/actions/**/*.yml" + - ".github/workflows/**/*.yml" +--- + +# Build and Packaging Steps Pattern + +## Important Rule + +**Build and packaging must run in the same step OR you must save and restore PSOptions between steps.** + +## Why This Matters + +When `Start-PSBuild` runs, it creates PSOptions that contain build configuration details (runtime, configuration, output path, etc.). The packaging functions like `Start-PSPackage` and `Invoke-CIFinish` rely on these PSOptions to know where the build output is located and how it was built. + +GitHub Actions steps run in separate PowerShell sessions. This means PSOptions from one step are not available in the next step. + +## Pattern 1: Combined Build and Package (Recommended) + +Run build and packaging in the same step to keep PSOptions in memory: + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +**Benefits:** +- Simpler code +- No need for intermediate files +- PSOptions automatically available to packaging + +## Pattern 2: Separate Steps with Save/Restore + +If you must separate build and packaging into different steps: + +```yaml +- name: Build PowerShell + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Save-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + shell: pwsh + +- name: Create Packages + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + Invoke-CIFinish + shell: pwsh +``` + +**When to use:** +- When you need to run other steps between build and packaging +- When build and packaging require different permissions or environments + +## Common Mistakes + +### ❌ Incorrect: Separate steps without save/restore + +```yaml +- name: Build PowerShell + run: |- + Start-PSBuild -Configuration 'Release' + shell: pwsh + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not available + shell: pwsh +``` + +### ❌ Incorrect: Using artifacts without PSOptions + +```yaml +- name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + name: build + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not restored + shell: pwsh +``` + +## Related Functions + +- `Start-PSBuild` - Builds PowerShell and sets PSOptions +- `Save-PSOptions` - Saves PSOptions to a JSON file +- `Restore-PSOptions` - Loads PSOptions from a JSON file +- `Get-PSOptions` - Gets current PSOptions +- `Set-PSOptions` - Sets PSOptions +- `Start-PSPackage` - Creates packages (requires PSOptions) +- `Invoke-CIFinish` - Calls packaging (requires PSOptions on Linux/macOS) + +## Examples + +### Linux Packaging Action + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +### Windows Packaging Workflow + +```yaml +- name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh +``` + +Note: `Invoke-CIFinish` for Windows includes both build and packaging in its logic when `Stage` contains 'Build'. diff --git a/.github/instructions/build-checkout-prerequisites.instructions.md b/.github/instructions/build-checkout-prerequisites.instructions.md new file mode 100644 index 00000000000..717aa6faa36 --- /dev/null +++ b/.github/instructions/build-checkout-prerequisites.instructions.md @@ -0,0 +1,148 @@ +--- +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Build and Checkout Prerequisites for PowerShell CI + +This document describes the checkout and build prerequisites used in PowerShell's CI workflows. It is intended for GitHub Copilot sessions working with the build system. + +## Overview + +The PowerShell repository uses a standardized build process across Linux, Windows, and macOS CI workflows. Understanding the checkout configuration and the `Sync-PSTags` operation is crucial for working with the build system. + +## Checkout Configuration + +### Fetch Depth + +All CI workflows that build or test PowerShell use `fetch-depth: 1000` in the checkout step: + +```yaml +- name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1000 +``` + +**Why 1000 commits?** +- The build system needs access to Git history to determine version information +- `Sync-PSTags` requires sufficient history to fetch and work with tags +- 1000 commits provides a reasonable balance between clone speed and having enough history for version calculation +- Shallow clones (fetch-depth: 1) would break versioning logic + +**Exceptions:** +- The `changes` job uses default fetch depth (no explicit `fetch-depth`) since it only needs to detect file changes +- The `analyze` job (CodeQL) uses `fetch-depth: '0'` (full history) for comprehensive security analysis +- Linux packaging uses `fetch-depth: 0` to ensure all tags are available for package version metadata + +### Workflows Using fetch-depth: 1000 + +- **Linux CI** (`.github/workflows/linux-ci.yml`): All build and test jobs +- **Windows CI** (`.github/workflows/windows-ci.yml`): All build and test jobs +- **macOS CI** (`.github/workflows/macos-ci.yml`): All build and test jobs + +## Sync-PSTags Operation + +### What is Sync-PSTags? + +`Sync-PSTags` is a PowerShell function defined in `build.psm1` that ensures Git tags from the upstream PowerShell repository are synchronized to the local clone. + +### Location + +- **Function Definition**: `build.psm1` (line 36-76) +- **Called From**: + - `.github/actions/build/ci/action.yml` (Bootstrap step, line 24) + - `tools/ci.psm1` (Invoke-CIInstall function, line 146) + +### How It Works + +```powershell +Sync-PSTags -AddRemoteIfMissing +``` + +The function: +1. Searches for a Git remote pointing to the official PowerShell repository: + - `https://github.com/PowerShell/PowerShell` + - `git@github.com:PowerShell/PowerShell` + +2. If no upstream remote exists and `-AddRemoteIfMissing` is specified: + - Adds a remote named `upstream` pointing to `https://github.com/PowerShell/PowerShell.git` + +3. Fetches all tags from the upstream remote: + ```bash + git fetch --tags --quiet upstream + ``` + +4. Sets `$script:tagsUpToDate = $true` to indicate tags are synchronized + +### Why Sync-PSTags is Required + +Tags are critical for: +- **Version Calculation**: `Get-PSVersion` uses `git describe --abbrev=0` to find the latest tag +- **Build Numbering**: CI builds use tag-based versioning for artifacts +- **Changelog Generation**: Release notes are generated based on tags +- **Package Metadata**: Package versions are derived from Git tags + +Without synchronized tags: +- Version detection would fail or return incorrect versions +- Builds might have inconsistent version numbers +- The build process would error when trying to determine the version + +### Bootstrap Step in CI Action + +The `.github/actions/build/ci/action.yml` includes this in the Bootstrap step: + +```yaml +- name: Bootstrap + if: success() + run: |- + Write-Verbose -Verbose "Running Bootstrap..." + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" + shell: pwsh +``` + +**Note**: `Sync-PSTags` is called twice: +1. Once by `Invoke-CIInstall` (in `tools/ci.psm1`) +2. Explicitly again in the Bootstrap step + +This redundancy ensures tags are available even if the first call encounters issues. + +## Best Practices for Copilot Sessions + +When working with the PowerShell CI system: + +1. **Always use `fetch-depth: 1000` or greater** when checking out code for build or test operations +2. **Understand that `Sync-PSTags` requires network access** to fetch tags from the upstream repository +3. **Don't modify the fetch-depth without understanding the impact** on version calculation +4. **If adding new CI workflows**, follow the existing pattern: + - Use `fetch-depth: 1000` for build/test jobs + - Call `Sync-PSTags -AddRemoteIfMissing` during bootstrap + - Ensure the upstream remote is properly configured + +5. **For local development**, developers should: + - Have the upstream remote configured + - Run `Sync-PSTags -AddRemoteIfMissing` before building + - Or use `Start-PSBuild` which handles this automatically + +## Related Files + +- `.github/actions/build/ci/action.yml` - Main CI build action +- `.github/workflows/linux-ci.yml` - Linux CI workflow +- `.github/workflows/windows-ci.yml` - Windows CI workflow +- `.github/workflows/macos-ci.yml` - macOS CI workflow +- `build.psm1` - Contains Sync-PSTags function definition +- `tools/ci.psm1` - CI-specific build functions that call Sync-PSTags + +## Summary + +The PowerShell CI system depends on: +1. **Adequate Git history** (fetch-depth: 1000) for version calculation +2. **Synchronized Git tags** via `Sync-PSTags` for accurate versioning +3. **Upstream remote access** to fetch official repository tags + +These prerequisites ensure consistent, accurate build versioning across all CI platforms. diff --git a/.github/instructions/build-configuration-guide.instructions.md b/.github/instructions/build-configuration-guide.instructions.md new file mode 100644 index 00000000000..d0384f4f307 --- /dev/null +++ b/.github/instructions/build-configuration-guide.instructions.md @@ -0,0 +1,150 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" + - ".pipelines/**/*.yml" +--- + +# Build Configuration Guide + +## Choosing the Right Configuration + +### For Testing + +**Use: Default (Debug)** + +```yaml +- name: Build for Testing + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +**Why Debug:** +- Includes debugging symbols +- Better error messages +- Faster build times +- Suitable for xUnit and Pester tests + +**Do NOT use:** +- `-Configuration 'Release'` (unnecessary for tests) +- `-ReleaseTag` (not needed for tests) +- `-CI` (unless you specifically need Pester module) + +### For Release/Packaging + +**Use: Release with version tag and public NuGet feeds** + +```yaml +- name: Build for Release + shell: pwsh + run: | + Import-Module ./build.psm1 + Import-Module ./tools/ci.psm1 + Switch-PSNugetConfig -Source Public + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +**Why Release:** +- Optimized binaries +- No debug symbols (smaller size) +- Production-ready + +**Why Switch-PSNugetConfig -Source Public:** +- Switches NuGet package sources to public feeds (nuget.org and public Azure DevOps feeds) +- Required for CI/CD environments that don't have access to private feeds +- Uses publicly available packages instead of Microsoft internal feeds + +### For Code Coverage + +**Use: CodeCoverage configuration** + +```yaml +- name: Build with Coverage + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild -Configuration 'CodeCoverage' +``` + +## Platform Considerations + +### All Platforms + +Same commands work across Linux, Windows, and macOS: + +```yaml +strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] +runs-on: ${{ matrix.os }} +steps: + - name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +### Output Locations + +**Linux/macOS:** +``` +src/powershell-unix/bin/Debug///publish/ +``` + +**Windows:** +``` +src/powershell-win-core/bin/Debug///publish/ +``` + +## Best Practices + +1. Use default configuration for testing +2. Avoid redundant parameters +3. Match configuration to purpose +4. Use `-CI` only when needed +5. Always specify `-ReleaseTag` for release or packaging builds +6. Use `Switch-PSNugetConfig -Source Public` in CI/CD for release builds + +## NuGet Feed Configuration + +### Switch-PSNugetConfig + +The `Switch-PSNugetConfig` function in `build.psm1` manages NuGet package source configuration. + +**Available Sources:** + +- **Public**: Uses public feeds (nuget.org and public Azure DevOps feeds) + - Required for: CI/CD environments, public builds, packaging + - Does not require authentication + +- **Private**: Uses internal PowerShell team feeds + - Required for: Internal development with preview packages + - Requires authentication credentials + +- **NuGetOnly**: Uses only nuget.org + - Required for: Minimal dependency scenarios + +**Usage:** + +```powershell +# Switch to public feeds (most common for CI/CD) +Switch-PSNugetConfig -Source Public + +# Switch to private feeds with authentication +Switch-PSNugetConfig -Source Private -UserName $userName -ClearTextPAT $pat + +# Switch to nuget.org only +Switch-PSNugetConfig -Source NuGetOnly +``` + +**When to Use:** + +- **Always use `-Source Public`** before building in CI/CD workflows +- Use before any build that will create packages for distribution +- Use in forks or environments without access to Microsoft internal feeds diff --git a/.github/instructions/code-review-branch-strategy.instructions.md b/.github/instructions/code-review-branch-strategy.instructions.md new file mode 100644 index 00000000000..191a677b912 --- /dev/null +++ b/.github/instructions/code-review-branch-strategy.instructions.md @@ -0,0 +1,230 @@ +--- +applyTo: "**/*" +--- + +# Code Review Branch Strategy Guide + +This guide helps GitHub Copilot provide appropriate feedback when reviewing code changes, particularly distinguishing between issues that should be fixed in the current branch versus the default branch. + +## Purpose + +When reviewing pull requests, especially those targeting release branches, it's important to identify whether an issue should be fixed in: +- **The current PR/branch** - Release-specific fixes or backports +- **The default branch first** - General bugs that exist in the main codebase + +## Branch Types and Fix Strategy + +### Release Branches (e.g., `release/v7.5`, `release/v7.4`) + +**Purpose:** Contain release-specific changes and critical backports + +**Should contain:** +- Release-specific configuration changes +- Critical bug fixes that are backported from the default branch +- Release packaging/versioning adjustments + +**Should NOT contain:** +- New general bug fixes that haven't been fixed in the default branch +- Refactoring or improvements that apply to the main codebase +- Workarounds for issues that exist in the default branch + +### Default/Main Branch (e.g., `master`, `main`) + +**Purpose:** Primary development branch for all ongoing work + +**Should contain:** +- All general bug fixes +- New features and improvements +- Refactoring and code quality improvements +- Fixes that will later be backported to release branches + +## Identifying Issues That Belong in the Default Branch + +When reviewing a PR targeting a release branch, look for these indicators that suggest the fix should be in the default branch first: + +### 1. The Root Cause Exists in Default Branch + +If the underlying issue exists in the default branch's code, it should be fixed there first. + +**Example:** +```yaml +# PR changes this in release/v7.5: +- $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json ++ $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json +``` + +**Analysis:** If `$repoRoot` is undefined because the template doesn't include its dependencies in BOTH the release branch AND the default branch, the fix should address the root cause in the default branch first. + +### 2. The Fix is a Workaround Rather Than a Proper Solution + +If the change introduces a workaround (hardcoded paths, special cases) rather than fixing the underlying design issue, it likely belongs in the default branch as a proper fix. + +**Example:** +- Using hardcoded paths instead of fixing variable initialization +- Adding special cases instead of fixing the logic +- Duplicating code instead of fixing shared dependencies + +### 3. The Issue Affects General Functionality + +If the issue affects general functionality not specific to a release, it should be fixed in the default branch. + +**Example:** +- Template dependencies that affect all pipelines +- Shared utility functions +- Common configuration issues + +## Providing Code Review Feedback + +### For Issues in the Current Branch + +When an issue is specific to the current branch or is a legitimate fix for the branch being targeted, **use the default code review feedback format** without any special branch-strategy commentary. + +### For Issues That Belong in the Default Branch + +1. **Provide the code review feedback** +2. **Explain why it should be fixed in the default branch** +3. **Provide an issue template** in markdown format + +**Example:** + +```markdown +The `channelSelection.yml` template relies on `$repoRoot` being set by `SetVersionVariables.yml`, but doesn't declare this dependency. This issue exists in both the release branch and the default branch. + +**This should be fixed in the default branch first**, then backported if needed. The proper fix is to ensure template dependencies are correctly declared, rather than using hardcoded paths as a workaround. + +--- + +**Suggested Issue for Default Branch:** + +### Issue Title +`channelSelection.yml` template missing dependency on `SetVersionVariables.yml` + +### Description +The `channelSelection.yml` template uses the `$repoRoot` variable but doesn't ensure it's set beforehand by including `SetVersionVariables.yml`. + +**Current State:** +- `channelSelection.yml` expects `$repoRoot` to be available +- Not all pipelines that use `channelSelection.yml` include `SetVersionVariables.yml` first +- This creates an implicit dependency that's not enforced + +**Expected State:** +Either: +1. `channelSelection.yml` should include `SetVersionVariables.yml` as a dependency, OR +2. `channelSelection.yml` should be refactored to not depend on `$repoRoot`, OR +3. Pipelines using `channelSelection.yml` should explicitly include `SetVersionVariables.yml` first + +**Files Affected:** +- `.pipelines/templates/channelSelection.yml` +- `.pipelines/templates/package-create-msix.yml` +- `.pipelines/templates/release-SetTagAndChangelog.yml` + +**Priority:** Medium +**Labels:** `Issue-Bug`, `Area-Build`, `Area-Pipeline` +``` + +## Issue Template Format + +When creating an issue template for the default branch, use this structure: + +```markdown +### Issue Title +[Clear, concise description of the problem] + +### Description +[Detailed explanation of the issue] + +**Current State:** +- [What's happening now] +- [Why it's problematic] + +**Expected State:** +- [What should happen] +- [Proposed solution(s)] + +**Files Affected:** +- [List of files] + +**Priority:** [Low/Medium/High/Critical] +**Labels:** [Suggested labels like `Issue-Bug`, `Area-*`] + +**Additional Context:** +[Any additional information, links to related issues, etc.] +``` + +## Common Scenarios + +### Scenario 1: Template Dependency Issues + +**Indicators:** +- Missing template includes +- Undefined variables from other templates +- Assumptions about pipeline execution order + +**Action:** Suggest fixing template dependencies in the default branch. + +### Scenario 2: Hardcoded Values + +**Indicators:** +- Hardcoded paths replacing variables +- Environment-specific values in shared code +- Magic strings or numbers + +**Action:** Suggest proper variable/parameter usage in the default branch. + +### Scenario 3: Logic Errors + +**Indicators:** +- Incorrect conditional logic +- Missing error handling +- Race conditions + +**Action:** Suggest fixing the logic in the default branch unless it's release-specific. + +### Scenario 4: Legitimate Release Branch Fixes + +**Indicators:** +- Version-specific configuration +- Release packaging changes +- Backport of already-fixed default branch issue + +**Action:** Provide normal code review feedback for the current PR. + +## Best Practices + +1. **Always check if the issue exists in the default branch** before suggesting a release-branch-only fix +2. **Prefer fixing root causes over workarounds** +3. **Provide clear rationale** for why a fix belongs in the default branch +4. **Include actionable issue templates** so users can easily create issues +5. **Be helpful, not blocking** - provide the feedback even if you can't enforce where it's fixed + +## Examples of Good vs. Bad Approaches + +### ❌ Bad: Workaround in Release Branch Only + +```yaml +# In release/v7.5 only +- pwsh: | + $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw +``` + +**Why bad:** Hardcodes path to work around missing `$repoRoot`, doesn't fix the default branch. + +### ✅ Good: Fix in Default Branch, Then Backport + +```yaml +# In default branch first +- template: SetVersionVariables.yml@self # Ensures $repoRoot is set +- template: channelSelection.yml@self # Now can use $repoRoot +``` + +**Why good:** Fixes the root cause by ensuring dependencies are declared, then backport to release if needed. + +## When in Doubt + +If you're unsure whether an issue should be fixed in the current branch or the default branch, ask yourself: + +1. Does this issue exist in the default branch? +2. Is this a workaround or a proper fix? +3. Will other branches/releases benefit from this fix? + +If the answer to any of these is "yes," suggest fixing it in the default branch first. diff --git a/.github/instructions/instruction-file-format.instructions.md b/.github/instructions/instruction-file-format.instructions.md new file mode 100644 index 00000000000..7c4e0bdd13d --- /dev/null +++ b/.github/instructions/instruction-file-format.instructions.md @@ -0,0 +1,220 @@ +--- +applyTo: + - ".github/instructions/**/*.instructions.md" +--- + +# Instruction File Format Guide + +This document describes the format and guidelines for creating custom instruction files for GitHub Copilot in the PowerShell repository. + +## File Naming Convention + +All instruction files must use the `.instructions.md` suffix: +- ✅ Correct: `build-checkout-prerequisites.instructions.md` +- ✅ Correct: `start-psbuild-basics.instructions.md` +- ❌ Incorrect: `build-guide.md` +- ❌ Incorrect: `instructions.md` + +## Required Frontmatter + +Every instruction file must start with YAML frontmatter containing an `applyTo` section: + +```yaml +--- +applyTo: + - "path/to/files/**/*.ext" + - "specific-file.ext" +--- +``` + +### applyTo Patterns + +Specify which files or directories these instructions apply to: + +**For workflow files:** +```yaml +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +``` + +**For build scripts:** +```yaml +applyTo: + - "build.psm1" + - "tools/ci.psm1" +``` + +**For multiple contexts:** +```yaml +applyTo: + - "build.psm1" + - "tools/**/*.psm1" + - ".github/**/*.yml" +``` + +## Content Structure + +### 1. Clear Title + +Use a descriptive H1 heading after the frontmatter: + +```markdown +# Build Configuration Guide +``` + +### 2. Purpose or Overview + +Start with a brief explanation of what the instructions cover: + +```markdown +## Purpose + +This guide explains how to configure PowerShell builds for different scenarios. +``` + +### 3. Actionable Content + +Provide clear, actionable guidance: + +**✅ Good - Specific and actionable:** +```markdown +## Default Usage + +Use `Start-PSBuild` with no parameters for testing: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` +``` + +**❌ Bad - Vague and unclear:** +```markdown +## Usage + +You can use Start-PSBuild to build stuff. +``` + +### 4. Code Examples + +Include working code examples with proper syntax highlighting: + +```markdown +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` +``` + +### 5. Context and Rationale + +Explain why things are done a certain way: + +```markdown +**Why fetch-depth: 1000?** +- The build system needs Git history for version calculation +- Shallow clones would break versioning logic +``` + +## Best Practices + +### Be Concise + +- Focus on essential information +- Remove redundant explanations +- Use bullet points for lists + +### Be Specific + +- Provide exact commands and parameters +- Include file paths and line numbers when relevant +- Show concrete examples, not abstract concepts + +### Avoid Duplication + +- Don't repeat information from other instruction files +- Reference other files when appropriate +- Keep each file focused on one topic + +### Use Proper Formatting + +**Headers:** +- Use H1 (`#`) for the main title +- Use H2 (`##`) for major sections +- Use H3 (`###`) for subsections + +**Code blocks:** +- Always specify the language: ` ```yaml `, ` ```powershell `, ` ```bash ` +- Keep examples short and focused +- Test examples before including them + +**Lists:** +- Use `-` for unordered lists +- Use `1.` for ordered lists +- Keep list items concise + +## Example Structure + +```markdown +--- +applyTo: + - "relevant/files/**/*.ext" +--- + +# Title of Instructions + +Brief description of what these instructions cover. + +## Section 1 + +Content with examples. + +```language +code example +``` + +## Section 2 + +More specific guidance. + +### Subsection + +Detailed information when needed. + +## Best Practices + +- Actionable tip 1 +- Actionable tip 2 +``` + +## Maintaining Instructions + +### When to Create a New File + +Create a new instruction file when: +- Covering a distinct topic not addressed elsewhere +- The content is substantial enough to warrant its own file +- The `applyTo` scope is different from existing files + +### When to Update an Existing File + +Update an existing file when: +- Information is outdated +- New best practices emerge +- Examples need correction + +### When to Merge or Delete + +Merge or delete files when: +- Content is duplicated across multiple files +- A file is too small to be useful standalone +- Information is no longer relevant + +## Reference + +For more details, see: +- [GitHub Copilot Custom Instructions Documentation](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions) diff --git a/.github/instructions/log-grouping-guidelines.instructions.md b/.github/instructions/log-grouping-guidelines.instructions.md new file mode 100644 index 00000000000..ff845db4e4b --- /dev/null +++ b/.github/instructions/log-grouping-guidelines.instructions.md @@ -0,0 +1,181 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Log Grouping Guidelines for GitHub Actions + +## Purpose + +Guidelines for using `Write-LogGroupStart` and `Write-LogGroupEnd` to create collapsible log sections in GitHub Actions CI/CD runs. + +## Key Principles + +### 1. Groups Cannot Be Nested + +GitHub Actions does not support nested groups. Only use one level of grouping. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Outer Group" +Write-LogGroupStart -Title "Inner Group" +# ... operations ... +Write-LogGroupEnd -Title "Inner Group" +Write-LogGroupEnd -Title "Outer Group" +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Operation A" +# ... operations ... +Write-LogGroupEnd -Title "Operation A" + +Write-LogGroupStart -Title "Operation B" +# ... operations ... +Write-LogGroupEnd -Title "Operation B" +``` + +### 2. Groups Should Be Substantial + +Only create groups for operations that generate substantial output (5+ lines). Small groups add clutter without benefit. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Generate Resource Files" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files" +``` + +**✅ Do:** +```powershell +Write-Log -message "Run ResGen (generating C# bindings for resx files)" +Start-ResGen +``` + +### 3. Groups Should Represent Independent Operations + +Each group should be a logically independent operation that users might want to expand/collapse separately. + +**✅ Good examples:** +- Install Native Dependencies +- Install .NET SDK +- Build PowerShell +- Restore NuGet Packages + +**❌ Bad examples:** +- Individual project restores (too granular) +- Small code generation steps (too small) +- Sub-steps of a larger operation (would require nesting) + +### 4. One Group Per Iteration Is Excessive + +Avoid putting log groups inside loops where each iteration creates a separate group. This would probably cause nesting. + +**❌ Don't:** +```powershell +$projects | ForEach-Object { + Write-LogGroupStart -Title "Restore Project: $_" + dotnet restore $_ + Write-LogGroupEnd -Title "Restore Project: $_" +} +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Restore All Projects" +$projects | ForEach-Object { + Write-Log -message "Restoring $_" + dotnet restore $_ +} +Write-LogGroupEnd -Title "Restore All Projects" +``` + +## Usage Pattern + +```powershell +Write-LogGroupStart -Title "Descriptive Operation Name" +try { + # ... operation code ... + Write-Log -message "Status updates" +} +finally { + # Ensure group is always closed +} +Write-LogGroupEnd -Title "Descriptive Operation Name" +``` + +## When to Use Log Groups + +Use log groups for: +- Major build phases (bootstrap, restore, build, test, package) +- Installation operations (dependencies, SDKs, tools) +- Operations that produce 5+ lines of output +- Operations where users might want to collapse verbose output + +Don't use log groups for: +- Single-line operations +- Code that's already inside another group +- Loop iterations with minimal output per iteration +- Diagnostic or debug output that should always be visible + +## Examples from build.psm1 + +### Good Usage + +```powershell +function Start-PSBootstrap { + # Multiple independent operations, each with substantial output + Write-LogGroupStart -Title "Install Native Dependencies" + # ... apt-get/yum/brew install commands ... + Write-LogGroupEnd -Title "Install Native Dependencies" + + Write-LogGroupStart -Title "Install .NET SDK" + # ... dotnet installation ... + Write-LogGroupEnd -Title "Install .NET SDK" +} +``` + +### Avoid + +```powershell +# Too small - just 2-3 lines +Write-LogGroupStart -Title "Generate Resource Files (ResGen)" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files (ResGen)" +``` + +## GitHub Actions Syntax + +These functions emit GitHub Actions workflow commands: +- `Write-LogGroupStart` → `::group::Title` +- `Write-LogGroupEnd` → `::endgroup::` + +In the GitHub Actions UI, this renders as collapsible sections with the specified title. + +## Testing + +Test log grouping locally: +```powershell +$env:GITHUB_ACTIONS = 'true' +Import-Module ./build.psm1 +Write-LogGroupStart -Title "Test" +Write-Log -Message "Content" +Write-LogGroupEnd -Title "Test" +``` + +Output should show: +``` +::group::Test +Content +::endgroup:: +``` + +## References + +- [GitHub Actions: Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines) +- `build.psm1`: `Write-LogGroupStart` and `Write-LogGroupEnd` function definitions diff --git a/.github/instructions/onebranch-condition-syntax.instructions.md b/.github/instructions/onebranch-condition-syntax.instructions.md new file mode 100644 index 00000000000..19bf331d9c3 --- /dev/null +++ b/.github/instructions/onebranch-condition-syntax.instructions.md @@ -0,0 +1,223 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Pipeline Condition Syntax + +## Overview +Azure Pipelines (OneBranch) uses specific syntax for referencing variables and parameters in condition expressions. Using the wrong syntax will cause conditions to fail silently or behave unexpectedly. + +## Variable Reference Patterns + +### In Condition Expressions + +**✅ Correct Pattern:** +```yaml +condition: eq(variables['VariableName'], 'value') +condition: or(eq(variables['VAR1'], 'true'), eq(variables['VAR2'], 'true')) +condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +**❌ Incorrect Patterns:** +```yaml +# Don't use $(VAR) string expansion in conditions +condition: eq('$(VariableName)', 'value') + +# Don't use direct variable references +condition: eq($VariableName, 'value') +``` + +### In Script Content (pwsh, bash, etc.) + +**✅ Correct Pattern:** +```yaml +- pwsh: | + $value = '$(VariableName)' + Write-Host "Value: $(VariableName)" +``` + +### In Input Fields + +**✅ Correct Pattern:** +```yaml +inputs: + serviceEndpoint: '$(ServiceEndpoint)' + sbConfigPath: '$(SBConfigPath)' +``` + +## Parameter References + +### Template Parameters (Compile-Time) + +**✅ Correct Pattern:** +```yaml +parameters: + - name: OfficialBuild + type: boolean + default: false + +steps: + - task: SomeTask@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') +``` + +Note: Parameters use `${{ parameters.Name }}` because they're evaluated at template compile-time. + +### Runtime Variables (Execution-Time) + +**✅ Correct Pattern:** +```yaml +steps: + - pwsh: | + Write-Host "##vso[task.setvariable variable=MyVar]somevalue" + displayName: Set Variable + + - task: SomeTask@1 + condition: eq(variables['MyVar'], 'somevalue') +``` + +## Common Scenarios + +### Scenario 1: Check if Variable Equals Value + +```yaml +- task: DoSomething@1 + condition: eq(variables['PREVIEW'], 'true') +``` + +### Scenario 2: Multiple Variable Conditions (OR) + +```yaml +- task: DoSomething@1 + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) +``` + +### Scenario 3: Multiple Variable Conditions (AND) + +```yaml +- task: DoSomething@1 + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +### Scenario 4: Complex Conditions + +```yaml +- task: DoSomething@1 + condition: and( + succeededOrFailed(), + ne(variables['UseAzDevOpsFeed'], ''), + eq(variables['Build.SourceBranch'], 'refs/heads/master') + ) +``` + +### Scenario 5: Built-in Variables + +```yaml +- task: CodeQL3000Init@0 + condition: eq(variables['Build.SourceBranch'], 'refs/heads/master') + +- step: finalize + condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues') +``` + +### Scenario 6: Parameter vs Variable + +```yaml +parameters: + - name: OfficialBuild + type: boolean + +steps: + # Parameter condition (compile-time) + - task: SignFiles@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') + + # Variable condition (runtime) + - task: PublishArtifact@1 + condition: eq(variables['PUBLISH_ENABLED'], 'true') +``` + +## Why This Matters + +**String Expansion `$(VAR)` in Conditions:** +- When you use `'$(VAR)'` in a condition, Azure Pipelines attempts to expand it as a string +- If the variable is undefined or empty, it becomes an empty string `''` +- The condition `eq('', 'true')` will always be false +- This makes debugging difficult because there's no error message + +**Variables Array Syntax `variables['VAR']`:** +- This is the proper way to reference runtime variables in conditions +- Azure Pipelines correctly evaluates the variable's value +- Undefined variables are handled properly by the condition evaluator +- This is the standard pattern used throughout Azure Pipelines + +## Reference Examples + +Working examples can be found in: +- `.pipelines/templates/linux.yml` - Build.SourceBranch conditions +- `.pipelines/templates/windows-hosted-build.yml` - Architecture conditions +- `.pipelines/templates/compliance/apiscan.yml` - CODEQL_ENABLED conditions +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Complex AND/OR conditions + +## Quick Reference Table + +| Context | Syntax | Example | +|---------|--------|---------| +| Condition expression | `variables['Name']` | `condition: eq(variables['PREVIEW'], 'true')` | +| Script content | `$(Name)` | `pwsh: Write-Host "$(PREVIEW)"` | +| Task input | `$(Name)` | `inputs: path: '$(Build.SourcesDirectory)'` | +| Template parameter | `${{ parameters.Name }}` | `condition: eq('${{ parameters.Official }}', 'true')` | + +## Troubleshooting + +### Condition Always False +If your condition is always evaluating to false: +1. Check if you're using `'$(VAR)'` instead of `variables['VAR']` +2. Verify the variable is actually set (add a debug step to print the variable) +3. Check the variable value is exactly what you expect (case-sensitive) + +### Variable Not Found +If you get errors about variables not being found: +1. Ensure the variable is set before the condition is evaluated +2. Check that the variable name is spelled correctly +3. Verify the variable is in scope (job vs. stage vs. pipeline level) + +## Best Practices + +1. **Always use `variables['Name']` in conditions** - This is the correct Azure Pipelines pattern +2. **Use `$(Name)` for string expansion** in scripts and inputs +3. **Use `${{ parameters.Name }}` for template parameters** (compile-time) +4. **Add debug steps** to verify variable values when troubleshooting conditions +5. **Follow existing patterns** in the repository - grep for `condition:` to see examples + +## Common Mistakes + +❌ **Mistake 1: String expansion in condition** +```yaml +condition: eq('$(PREVIEW)', 'true') # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq(variables['PREVIEW'], 'true') # CORRECT +``` + +❌ **Mistake 2: Missing quotes around parameter** +```yaml +condition: eq(${{ parameters.Official }}, true) # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq('${{ parameters.Official }}', 'true') # CORRECT +``` + +❌ **Mistake 3: Mixing syntax** +```yaml +condition: or(eq('$(STABLE)', 'true'), eq(variables['LTS'], 'true')) # INCONSISTENT +``` + +✅ **Fix:** +```yaml +condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) # CORRECT +``` diff --git a/.github/instructions/onebranch-restore-phase-pattern.instructions.md b/.github/instructions/onebranch-restore-phase-pattern.instructions.md new file mode 100644 index 00000000000..0945bb47c0b --- /dev/null +++ b/.github/instructions/onebranch-restore-phase-pattern.instructions.md @@ -0,0 +1,83 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Restore Phase Pattern + +## Overview +When steps need to run in the OneBranch restore phase (before the main build phase), the `ob_restore_phase` environment variable must be set in the `env:` block of **each individual step**. + +## Pattern + +### ✅ Correct (Working Pattern) +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # or false if you don't want restore phase + +steps: +- powershell: | + # script content + displayName: 'Step Name' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} +``` + +The key is to: +1. Define `ob_restore_phase` as a **boolean** parameter +2. Set `ob_restore_phase: ${{ parameters.ob_restore_phase }}` directly in each step's `env:` block +3. Pass `true` to run in restore phase, `false` to run in normal build phase + +### ❌ Incorrect (Does Not Work) +```yaml +steps: +- powershell: | + # script content + displayName: 'Step Name' + ${{ if eq(parameters.useRestorePhase, 'yes') }}: + env: + ob_restore_phase: true +``` + +Using conditionals at the same indentation level as `env:` causes only the first step to execute in restore phase. + +## Parameters + +Templates using this pattern should accept an `ob_restore_phase` boolean parameter: + +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # Set to true to run in restore phase by default +``` + +## Reference Examples + +Working examples of this pattern can be found in: +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Demonstrates the correct pattern +- `.pipelines/templates/SetVersionVariables.yml` - Updated to use this pattern + +## Why This Matters + +The restore phase in OneBranch pipelines runs before signing and other build operations. Steps that need to: +- Set environment variables for the entire build +- Configure authentication +- Prepare the repository structure + +Must run in the restore phase to be available when subsequent stages execute. + +## Common Use Cases + +- Setting `REPOROOT` variable +- Configuring NuGet feeds with authentication +- Setting version variables +- Repository preparation and validation + +## Troubleshooting + +If only the first step in your template is running in restore phase: +1. Check that `env:` block exists for **each step** +2. Verify the conditional `${{ if ... }}:` is **inside** the `env:` block +3. Confirm indentation is correct (conditional is indented under `env:`) diff --git a/.github/instructions/onebranch-signing-configuration.instructions.md b/.github/instructions/onebranch-signing-configuration.instructions.md new file mode 100644 index 00000000000..747fcaffdd6 --- /dev/null +++ b/.github/instructions/onebranch-signing-configuration.instructions.md @@ -0,0 +1,195 @@ +--- +applyTo: + - ".pipelines/**/*.yml" + - ".pipelines/**/*.yaml" +--- + +# OneBranch Signing Configuration + +This guide explains how to configure OneBranch signing variables in Azure Pipeline jobs, particularly when signing is not required. + +## Purpose + +OneBranch pipelines include signing infrastructure by default. For build-only jobs where signing happens in a separate stage, you should disable signing setup to improve performance and avoid unnecessary overhead. + +## Disable Signing for Build-Only Jobs + +When a job does not perform signing (e.g., it only builds artifacts that will be signed in a later stage), disable both signing setup and code sign validation: + +```yaml +variables: + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage +``` + +### Why Disable These Variables? + +**`ob_signing_setup_enabled: false`** +- Prevents OneBranch from setting up the signing infrastructure +- Reduces job startup time +- Avoids unnecessary credential validation +- Only disable when the job will NOT sign any artifacts + +**`ob_sdl_codeSignValidation_enabled: false`** +- Skips validation that checks if files are properly signed +- Appropriate for build stages where artifacts are unsigned +- Must be enabled in signing/release stages to validate signatures + +## Common Patterns + +### Build-Only Job (No Signing) + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + - pwsh: | + # Build unsigned artifacts + Start-PSBuild +``` + +### Signing Job + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true + - name: ob_sdl_codeSignValidation_enabled + value: true + steps: + - checkout: self + env: + ob_restore_phase: true # Steps before first signing operation + - pwsh: | + # Prepare artifacts for signing + env: + ob_restore_phase: true # Steps before first signing operation + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + # Signing step runs in build phase (no ob_restore_phase) + - pwsh: | + # Post-signing validation + # Post-signing steps run in build phase (no ob_restore_phase) +``` + +## Restore Phase Usage with Signing + +**The restore phase (`ob_restore_phase: true`) should only be used in jobs that perform signing operations.** It separates preparation steps from the actual signing and build steps. + +### When to Use Restore Phase + +Use `ob_restore_phase: true` **only** in jobs where `ob_signing_setup_enabled: true`: + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true # Signing enabled + steps: + # Steps BEFORE first signing operation: use restore phase + - checkout: self + env: + ob_restore_phase: true + - template: prepare-for-signing.yml + parameters: + ob_restore_phase: true + + # SIGNING STEP: runs in build phase (no ob_restore_phase) + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + + # Steps AFTER signing: run in build phase (no ob_restore_phase) + - pwsh: | + # Validation or packaging +``` + +### When NOT to Use Restore Phase + +**Do not use restore phase in build-only jobs** where `ob_signing_setup_enabled: false`: + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false # No signing + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + # NO ob_restore_phase - not needed without signing + - pwsh: | + Start-PSBuild +``` + +**Why?** The restore phase is part of OneBranch's signing infrastructure. Using it without signing enabled adds unnecessary overhead without benefit. + +## Related Variables + +Other OneBranch signing-related variables: + +- `ob_sdl_binskim_enabled`: Controls BinSkim security analysis (can be false in build-only, true in signing stages) + +## Best Practices + +1. **Separate build and signing stages**: Build artifacts in one job, sign in another +2. **Disable signing in build stages**: Improves performance and clarifies intent +3. **Only use restore phase with signing**: The restore phase should only be used in jobs where signing is enabled (`ob_signing_setup_enabled: true`) +4. **Restore phase before first signing step**: All steps before the first signing operation should use `ob_restore_phase: true` +5. **Always validate after signing**: Enable validation in signing stages to catch issues +6. **Document the reason**: Add comments explaining why signing is disabled or why restore phase is used + +## Example: Split Build and Sign Pipeline + +```yaml +stages: + - stage: Build + jobs: + - job: build_windows + variables: + - name: ob_signing_setup_enabled + value: false # Build-only, no signing + - name: ob_sdl_codeSignValidation_enabled + value: false # Artifacts are unsigned + steps: + - template: templates/build-unsigned.yml + + - stage: Sign + dependsOn: Build + jobs: + - job: sign_windows + variables: + - name: ob_signing_setup_enabled + value: true # Enable signing infrastructure + - name: ob_sdl_codeSignValidation_enabled + value: true # Validate signatures + steps: + - template: templates/sign-artifacts.yml +``` + +## Troubleshooting + +**Job fails with signing-related errors but signing is disabled:** +- Verify `ob_signing_setup_enabled: false` is set in variables +- Check that no template is overriding the setting +- Ensure `ob_sdl_codeSignValidation_enabled: false` is also set + +**Signed artifacts fail validation:** +- Confirm `ob_sdl_codeSignValidation_enabled: true` in signing job +- Verify signing actually occurred +- Check certificate configuration + +## Reference + +- PowerShell signing templates: `.pipelines/templates/packaging/windows/sign.yml` diff --git a/.github/instructions/pester-set-itresult-pattern.instructions.md b/.github/instructions/pester-set-itresult-pattern.instructions.md new file mode 100644 index 00000000000..33a73ca081d --- /dev/null +++ b/.github/instructions/pester-set-itresult-pattern.instructions.md @@ -0,0 +1,198 @@ +--- +applyTo: + - "**/*.Tests.ps1" +--- + +# Pester Set-ItResult Pattern for Pending and Skipped Tests + +## Purpose + +This instruction explains when and how to use `Set-ItResult` in Pester tests to mark tests as Pending or Skipped dynamically within test execution. + +## When to Use Set-ItResult + +Use `Set-ItResult` when you need to conditionally mark a test as Pending or Skipped based on runtime conditions that can't be determined at test definition time. + +### Pending vs Skipped + +**Pending**: Use for tests that should be enabled but temporarily can't run due to: +- Intermittent external service failures (network, APIs) +- Known bugs being fixed +- Missing features being implemented +- Environmental issues that are being resolved + +**Skipped**: Use for tests that aren't applicable to the current environment: +- Platform-specific tests running on wrong platform +- Tests requiring specific hardware/configuration not present +- Tests requiring elevated permissions when not available +- Feature-specific tests when feature is disabled + +## Pattern + +### Basic Usage + +```powershell +It "Test description" { + if ($shouldBePending) { + Set-ItResult -Pending -Because "Explanation of why test is pending" + return + } + + if ($shouldBeSkipped) { + Set-ItResult -Skipped -Because "Explanation of why test is skipped" + return + } + + # Test code here +} +``` + +### Important: Always Return After Set-ItResult + +After calling `Set-ItResult`, you **must** return from the test to prevent further execution: + +```powershell +It "Test that checks environment" { + if ($env:SKIP_TESTS -eq 'true') { + Set-ItResult -Skipped -Because "SKIP_TESTS environment variable is set" + return # This is required! + } + + # Test assertions + $result | Should -Be $expected +} +``` + +**Why?** Without `return`, the test continues executing and may fail with errors unrelated to the pending/skipped condition. + +## Examples from the Codebase + +### Example 1: Pending for Intermittent Network Issues + +```powershell +It "Validate Update-Help for module" { + if ($markAsPending) { + Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." + return + } + + Update-Help -Module $moduleName -Force + # validation code... +} +``` + +### Example 2: Skipped for Missing Environment + +```powershell +It "Test requires CI environment" { + if (-not $env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + return + } + + Install-CIPester -ErrorAction Stop +} +``` + +### Example 3: Pending for Platform-Specific Issue + +```powershell +It "Clear-Host works correctly" { + if ($IsARM64) { + Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work." + return + } + + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' +} +``` + +### Example 4: Skipped for Missing Feature + +```powershell +It "Test ACR authentication" { + if ($env:ACRTESTS -ne 'true') { + Set-ItResult -Skipped -Because "The tests require the ACRTESTS environment variable to be set to 'true' for ACR authentication." + return + } + + $psgetModuleInfo = Find-PSResource -Name $ACRTestModule -Repository $ACRRepositoryName + # test assertions... +} +``` + +## Alternative: Static -Skip and -Pending Parameters + +For conditions that can be determined at test definition time, use the static parameters instead: + +```powershell +# Static skip - condition known at definition time +It "Windows-only test" -Skip:(-not $IsWindows) { + # test code +} + +# Static pending - always pending +It "Test for feature being implemented" -Pending { + # test code that will fail until feature is done +} +``` + +**Use Set-ItResult when**: +- Condition depends on runtime state +- Condition is determined inside a helper function +- Need to check multiple conditions sequentially + +**Use static parameters when**: +- Condition is known at test definition +- Condition doesn't change during test run +- Want Pester to show the condition in test discovery + +## Best Practices + +1. **Always include -Because parameter** with a clear explanation +2. **Always return after Set-ItResult** to prevent further execution +3. **Reference issues or documentation** when relevant (e.g., "See issue #1234") +4. **Be specific in the reason** - explain what's wrong and what's needed +5. **Use Pending sparingly** - it indicates a problem that should be fixed +6. **Prefer Skipped over Pending** when test truly isn't applicable + +## Common Mistakes + +### ❌ Mistake 1: Forgetting to Return + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Reason" + # Missing return - test code will still execute! + } + $value | Should -Be $expected # This runs and fails +} +``` + +### ❌ Mistake 2: Vague Reason + +```powershell +Set-ItResult -Pending -Because "Doesn't work" # Too vague +``` + +### ✅ Correct: + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Update-Help has intermittent network timeouts. See issue #2807." + return + } + $value | Should -Be $expected +} +``` + +## See Also + +- [Pester Documentation: Set-ItResult](https://pester.dev/docs/commands/Set-ItResult) +- [Pester Documentation: It](https://pester.dev/docs/commands/It) +- Examples in the codebase: + - `test/powershell/Host/ConsoleHost.Tests.ps1` + - `test/infrastructure/ciModule.Tests.ps1` + - `tools/packaging/releaseTests/sbom.tests.ps1` diff --git a/.github/instructions/pester-test-status-and-working-meaning.instructions.md b/.github/instructions/pester-test-status-and-working-meaning.instructions.md new file mode 100644 index 00000000000..d2b28a05f18 --- /dev/null +++ b/.github/instructions/pester-test-status-and-working-meaning.instructions.md @@ -0,0 +1,299 @@ +--- +applyTo: "**/*.Tests.ps1" +--- + +# Pester Test Status Meanings and Working Tests + +## Purpose + +This guide clarifies Pester test outcomes and what it means for a test to be "working" - which requires both **passing** AND **actually validating functionality**. + +## Test Statuses in Pester + +### Passed ✓ +**Status Code**: `Passed` +**Exit Result**: Test ran successfully, all assertions passed + +**What it means**: +- Test executed without errors +- All `Should` statements evaluated to true +- Test setup and teardown completed without issues +- Test is **validating** the intended functionality + +**What it does NOT mean**: +- The feature is working (assertions could be wrong) +- The test is meaningful (could be testing wrong thing) +- The test exercises all code paths + +### Failed ✗ +**Status Code**: `Failed` +**Exit Result**: Test ran but assertions failed + +**What it means**: +- Test executed but an assertion returned false +- Expected value did not match actual value +- Test detected a problem with the functionality + +**Examples**: +``` +Expected $true but got $false +Expected 5 items but got 3 +Expected no error but got: Cannot find parameter +``` + +### Error ⚠ +**Status Code**: `Error` +**Exit Result**: Test crashed with an exception + +**What it means**: +- Test failed to complete +- An exception was thrown during test execution +- Could be in test setup, test body, or test cleanup +- Often indicates environmental issue, not code functional issue + +**Examples**: +``` +Cannot bind argument to parameter 'Path' because it is null +File not found: C:\expected\config.json +Access denied writing to registry +``` + +### Pending ⏳ +**Status Code**: `Pending` +**Exit Result**: Test ran but never completed assertions + +**What it means**: +- Test was explicitly marked as not ready to run +- `Set-ItResult -Pending` was called +- Used to indicate: known bugs, missing features, environmental issues + +**When to use Pending**: +- Test for feature in development +- Test disabled due to known bug (issue #1234) +- Test disabled due to intermittent failures being fixed +- Platform-specific issues being resolved + +**⚠️ WARNING**: Pending tests are NOT validating functionality. They hide problems. + +### Skipped ⊘ +**Status Code**: `Skipped` +**Exit Result**: Test did not run (detected at start) + +**What it means**: +- Test was intentionally not executed +- `-Skip` parameter or `It -Skip:$condition` was used +- Environment doesn't support this test + +**When to use Skip**: +- Test not applicable to current platform (Windows-only test on Linux) +- Test requires feature that's not available (admin privileges) +- Test requires specific configuration not present + +**Difference from Pending**: +- **Skip**: "This test shouldn't run here" (known upfront) +- **Pending**: "This test should eventually run but can't now" + +### Ignored ✛ +**Status Code**: `Ignored` +**Exit Result**: Test marked as not applicable + +**What it means**: +- Test has `[Ignore("reason")]` attribute +- Test is permanently disabled in this location +- Not the same as Skipped (which is conditional) + +**When to use Ignore**: +- Test for deprecated feature +- Test for bug that won't be fixed +- Test moved to different test file + +--- + +## What Does "Working" Actually Mean? + +A test is **working** when it meets BOTH criteria: + +### 1. **Test Status is PASSED** ✓ +```powershell +It "Test name" { + # Test executes + # All assertions pass + # Returns Passed status +} +``` + +### 2. **Test Actually Validates Functionality** +```powershell +# ✓ GOOD: Tests actual functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $testDir = New-Item -ItemType Directory -Force + New-Item -Path $testDir -Name "file.txt" -ItemType File | Out-Null + + $result = Get-Item -Path "$testDir\file.txt" + + $result.Name | Should -Be "file.txt" + $result | Should -Exist + + Remove-Item $testDir -Recurse -Force +} + +# ✗ BAD: Returns Passed but doesn't validate functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $result = Get-Item -Path somepath # May not exist, may not actually test + $result | Should -Not -BeNullOrEmpty # Too vague +} + +# ✗ BAD: Test marked Pending - validation is hidden +It "Get-Item returns files from directory" -Tags @('Unit') { + Set-ItResult -Pending -Because "File system not working" + return + # No validation happens at all +} +``` + +--- + +## The Problem with Pending Tests + +### Why Pending Tests Hide Problems + +```powershell +# BAD: Test marked Pending - looks like "working" status but validation is skipped +It "Download help from web" { + Set-ItResult -Pending -Because "Web connectivity issues" + return + + # This code never runs: + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Result**: +- ✗ Feature is broken (Update-Help fails) +- ✓ Test shows "Pending" (looks acceptable) +- ✗ Problem is hidden and never fixed + +### The Right Approach + +**Option A: Fix the root cause** +```powershell +It "Download help from web" { + # Use local assets that are guaranteed to work + Update-Help -Module PackageManagement -SourcePath ./assets -Force -ErrorAction Stop + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option B: Gracefully skip when unavailable** +```powershell +It "Download help from web" -Skip:$(-not $hasInternet) { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option C: Add retry logic for intermittent issues** +```powershell +It "Download help from web" { + $maxRetries = 3 + $attempt = 0 + + while ($attempt -lt $maxRetries) { + try { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + break + } + catch { + $attempt++ + if ($attempt -ge $maxRetries) { throw } + Start-Sleep -Seconds 2 + } + } + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +--- + +## Test Status Summary Table + +| Status | Passed? | Validates? | Counts as "Working"? | Use When | +|--------|---------|------------|----------------------|----------| +| **Passed** | ✓ | ✓ | **YES** | Feature is working and test proves it | +| **Failed** | ✗ | ✓ | NO | Feature is broken or test has wrong expectation | +| **Error** | ✗ | ✗ | NO | Test infrastructure broken, can't validate | +| **Pending** | - | ✗ | **NO** ⚠️ | Temporary - test should eventually pass | +| **Skipped** | - | ✗ | NO | Test not applicable to this environment | +| **Ignored** | - | ✗ | NO | Test permanently disabled | + +--- + +## Recommended Patterns + +### Pattern 1: Resilient Test with Fallback +```powershell +It "Feature works with web or local source" { + $useLocal = $false + + try { + Update-Help -Module Package -Force -ErrorAction Stop + } + catch { + $useLocal = $true + Update-Help -Module Package -SourcePath ./assets -Force -ErrorAction Stop + } + + # Validate functionality regardless of source + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +### Pattern 2: Conditional Skip with Clear Reason +```powershell +Describe "Update-Help from Web" -Skip $(-not (Test-InternetConnectivity)) { + It "Downloads help successfully" { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +### Pattern 3: Separate Suites by Dependency +```powershell +Describe "Help Content Tests - Web" { + # Tests that require internet - can be skipped if unavailable + It "Downloads from web" { ... } +} + +Describe "Help Content Tests - Local" { + # Tests with local assets - should always pass + It "Loads from local assets" { + Update-Help -Module Package -SourcePath ./assets -Force + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +--- + +## Checklist: Is Your Test "Working"? + +- [ ] Test status is **Passed** (not Pending, not Skipped, not Failed) +- [ ] Test actually **executes** the feature being tested +- [ ] Test has **specific assertions** (not just `Should -Not -BeNullOrEmpty`) +- [ ] Test includes **cleanup** (removes temp files, restores state) +- [ ] Test can run **multiple times** without side effects +- [ ] Test failure **indicates a real problem** (not flaky assertions) +- [ ] Test success **proves the feature works** (not just "didn't crash") + +If any of these is false, your test may be passing but not "working" properly. + +--- + +## See Also + +- [Pester Documentation](https://pester.dev/) +- [Set-ItResult Documentation](https://pester.dev/docs/commands/Set-ItResult) diff --git a/.github/instructions/powershell-automatic-variables.instructions.md b/.github/instructions/powershell-automatic-variables.instructions.md new file mode 100644 index 00000000000..5015847f41f --- /dev/null +++ b/.github/instructions/powershell-automatic-variables.instructions.md @@ -0,0 +1,159 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# PowerShell Automatic Variables - Naming Guidelines + +## Purpose + +This instruction provides guidelines for avoiding conflicts with PowerShell's automatic variables when writing PowerShell scripts and modules. + +## What Are Automatic Variables? + +PowerShell has built-in automatic variables that are created and maintained by PowerShell itself. Assigning values to these variables can cause unexpected behavior and side effects. + +## Common Automatic Variables to Avoid + +### Critical Variables (Never Use) + +- **`$matches`** - Contains the results of regular expression matches. Overwriting this can break regex operations. +- **`$_`** - Represents the current object in the pipeline. Only use within pipeline blocks. +- **`$PSItem`** - Alias for `$_`. Same rules apply. +- **`$args`** - Contains an array of undeclared parameters. Don't use as a regular variable. +- **`$input`** - Contains an enumerator of all input passed to a function. Don't reassign. +- **`$LastExitCode`** - Exit code of the last native command. Don't overwrite unless intentional. +- **`$?`** - Success status of the last command. Don't use as a variable name. +- **`$$`** - Last token in the last line received by the session. Don't use. +- **`$^`** - First token in the last line received by the session. Don't use. + +### Context Variables (Use with Caution) + +- **`$Error`** - Array of error objects. Don't replace, but can modify (e.g., `$Error.Clear()`). +- **`$PSBoundParameters`** - Parameters passed to the current function. Read-only. +- **`$MyInvocation`** - Information about the current command. Read-only. +- **`$PSCmdlet`** - Cmdlet object for advanced functions. Read-only. + +### Other Common Automatic Variables + +- `$true`, `$false`, `$null` - Boolean and null constants +- `$HOME`, `$PSHome`, `$PWD` - Path-related variables +- `$PID` - Process ID of the current PowerShell session +- `$Host` - Host application object +- `$PSVersionTable` - PowerShell version information + +For a complete list, see: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables + +## Best Practices + +### ❌ Bad - Using Automatic Variable Names + +```powershell +# Bad: $matches is an automatic variable used for regex capture groups +$matches = Select-String -Path $file -Pattern $pattern + +# Bad: $args is an automatic variable for undeclared parameters +$args = Get-ChildItem + +# Bad: $input is an automatic variable for pipeline input +$input = Read-Host "Enter value" +``` + +### ✅ Good - Using Descriptive Alternative Names + +```powershell +# Good: Use descriptive names that avoid conflicts +$matchedLines = Select-String -Path $file -Pattern $pattern + +# Good: Use specific names for arguments +$arguments = Get-ChildItem + +# Good: Use specific names for user input +$userInput = Read-Host "Enter value" +``` + +## Naming Alternatives + +When you encounter a situation where you might use an automatic variable name, use these alternatives: + +| Avoid | Use Instead | +|-------|-------------| +| `$matches` | `$matchedLines`, `$matchResults`, `$regexMatches` | +| `$args` | `$arguments`, `$parameters`, `$commandArgs` | +| `$input` | `$userInput`, `$inputValue`, `$inputData` | +| `$_` (outside pipeline) | Use a named parameter or explicit variable | +| `$Error` (reassignment) | Don't reassign; use `$Error.Clear()` if needed | + +## How to Check + +### PSScriptAnalyzer Rule + +PSScriptAnalyzer has a built-in rule that detects assignments to automatic variables: + +```powershell +# This will trigger PSAvoidAssignmentToAutomaticVariable +$matches = Get-Something +``` + +**Rule ID**: PSAvoidAssignmentToAutomaticVariable + +### Manual Review + +When writing PowerShell code, always: +1. Avoid variable names that match PowerShell keywords or automatic variables +2. Use descriptive, specific names that clearly indicate the variable's purpose +3. Run PSScriptAnalyzer on your code before committing +4. Review code for variable naming during PR reviews + +## Examples from the Codebase + +### Example 1: Regex Matching + +```powershell +# ❌ Bad - Overwrites automatic $matches variable +$matches = [regex]::Matches($content, $pattern) + +# ✅ Good - Uses descriptive name +$regexMatches = [regex]::Matches($content, $pattern) +``` + +### Example 2: Select-String Results + +```powershell +# ❌ Bad - Conflicts with automatic $matches +$matches = Select-String -Path $file -Pattern $pattern + +# ✅ Good - Clear and specific +$matchedLines = Select-String -Path $file -Pattern $pattern +``` + +### Example 3: Collecting Arguments + +```powershell +# ❌ Bad - Conflicts with automatic $args +function Process-Items { + $args = $MyItems + # ... process items +} + +# ✅ Good - Descriptive parameter name +function Process-Items { + [CmdletBinding()] + param( + [Parameter(ValueFromRemainingArguments)] + [string[]]$Items + ) + # ... process items +} +``` + +## References + +- [PowerShell Automatic Variables Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables) +- [PSScriptAnalyzer Rules](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/docs/Rules/README.md) +- [PowerShell Best Practices](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) + +## Summary + +**Key Takeaway**: Always use descriptive, specific variable names that clearly indicate their purpose and avoid conflicts with PowerShell's automatic variables. When in doubt, choose a longer, more descriptive name over a short one that might conflict. diff --git a/.github/instructions/powershell-module-organization.instructions.md b/.github/instructions/powershell-module-organization.instructions.md new file mode 100644 index 00000000000..9cdba06c364 --- /dev/null +++ b/.github/instructions/powershell-module-organization.instructions.md @@ -0,0 +1,201 @@ +--- +applyTo: + - "tools/ci.psm1" + - "build.psm1" + - "tools/packaging/**/*.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Guidelines for PowerShell Code Organization + +## When to Move Code from YAML to PowerShell Modules + +PowerShell code in GitHub Actions YAML files should be kept minimal. Move code to a module when: + +### Size Threshold +- **More than ~30 lines** of PowerShell in a YAML file step +- **Any use of .NET types** like `[regex]`, `[System.IO.Path]`, etc. +- **Complex logic** requiring multiple nested loops or conditionals +- **Reusable functionality** that might be needed elsewhere + +### Indicators to Move Code +1. Using .NET type accelerators (`[regex]`, `[PSCustomObject]`, etc.) +2. Complex string manipulation or parsing +3. File system operations beyond basic reads/writes +4. Logic that would benefit from unit testing +5. Code that's difficult to read/maintain in YAML format + +## Which Module to Use + +### ci.psm1 (`tools/ci.psm1`) +**Purpose**: CI/CD-specific operations and workflows + +**Use for**: +- Build orchestration (invoking builds, tests, packaging) +- CI environment setup and configuration +- Test execution and result processing +- Artifact handling and publishing +- CI-specific validations and checks +- Environment variable management for CI + +**Examples**: +- `Invoke-CIBuild` - Orchestrates build process +- `Invoke-CITest` - Runs Pester tests +- `Test-MergeConflictMarker` - Validates files for conflicts +- `Set-BuildVariable` - Manages CI variables + +**When NOT to use**: +- Core build operations (use build.psm1) +- Package creation logic (use packaging.psm1) +- Platform-specific build steps + +### build.psm1 (`build.psm1`) +**Purpose**: Core build operations and utilities + +**Use for**: +- Compiling source code +- Resource generation +- Build configuration management +- Core build utilities (New-PSOptions, Get-PSOutput, etc.) +- Bootstrap operations +- Cross-platform build helpers + +**Examples**: +- `Start-PSBuild` - Main build function +- `Start-PSBootstrap` - Bootstrap dependencies +- `New-PSOptions` - Create build configuration +- `Start-ResGen` - Generate resources + +**When NOT to use**: +- CI workflow orchestration (use ci.psm1) +- Package creation (use packaging.psm1) +- Test execution + +### packaging.psm1 (`tools/packaging/packaging.psm1`) +**Purpose**: Package creation and distribution + +**Use for**: +- Creating distribution packages (MSI, RPM, DEB, etc.) +- Package-specific metadata generation +- Package signing operations +- Platform-specific packaging logic + +**Examples**: +- `Start-PSPackage` - Create packages +- `New-MSIXPackage` - Create Windows MSIX +- `New-DotnetSdkContainerFxdPackage` - Create container packages + +**When NOT to use**: +- Building binaries (use build.psm1) +- Running tests (use ci.psm1) +- General utilities + +## Best Practices + +### Keep YAML Minimal +```yaml +# ❌ Bad - too much logic in YAML +- name: Check files + shell: pwsh + run: | + $files = Get-ChildItem -Recurse + foreach ($file in $files) { + $content = Get-Content $file -Raw + if ($content -match $pattern) { + # ... complex processing ... + } + } + +# ✅ Good - call function from module +- name: Check files + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Test-SomeCondition -Path ${{ github.workspace }} +``` + +### Document Functions +Always include comment-based help for functions: +```powershell +function Test-MyFunction +{ + <# + .SYNOPSIS + Brief description + .DESCRIPTION + Detailed description + .PARAMETER ParameterName + Parameter description + .EXAMPLE + Test-MyFunction -ParameterName Value + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $ParameterName + ) + # Implementation +} +``` + +### Error Handling +Use proper error handling in modules: +```powershell +try { + # Operation +} +catch { + Write-Error "Detailed error message: $_" + throw +} +``` + +### Verbose Output +Use `Write-Verbose` for debugging information: +```powershell +Write-Verbose "Processing file: $filePath" +``` + +## Module Dependencies + +- **ci.psm1** imports both `build.psm1` and `packaging.psm1` +- **build.psm1** is standalone (minimal dependencies) +- **packaging.psm1** imports `build.psm1` + +When adding new functions, consider these import relationships to avoid circular dependencies. + +## Testing Modules + +Functions in modules should be testable: +```powershell +# Test locally +Import-Module ./tools/ci.psm1 -Force +Test-MyFunction -Parameter Value + +# Can be unit tested with Pester +Describe "Test-MyFunction" { + It "Should return expected result" { + # Test implementation + } +} +``` + +## Migration Checklist + +When moving code from YAML to a module: + +1. ✅ Determine which module is appropriate (ci, build, or packaging) +2. ✅ Create function with proper parameter validation +3. ✅ Add comment-based help documentation +4. ✅ Use `[CmdletBinding()]` for advanced function features +5. ✅ Include error handling +6. ✅ Add verbose output for debugging +7. ✅ Test the function independently +8. ✅ Update YAML to call the new function +9. ✅ Verify the workflow still works end-to-end + +## References + +- PowerShell Advanced Functions: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced +- Comment-Based Help: https://learn.microsoft.com/powershell/scripting/developer/help/writing-help-for-windows-powershell-scripts-and-functions diff --git a/.github/instructions/powershell-parameter-naming.instructions.md b/.github/instructions/powershell-parameter-naming.instructions.md new file mode 100644 index 00000000000..155fd1a85c3 --- /dev/null +++ b/.github/instructions/powershell-parameter-naming.instructions.md @@ -0,0 +1,69 @@ +--- +applyTo: '**/*.ps1, **/*.psm1' +description: Naming conventions for PowerShell parameters +--- + +# PowerShell Parameter Naming Conventions + +## Purpose + +This instruction defines the naming conventions for parameters in PowerShell scripts and modules. Consistent parameter naming improves code readability, maintainability, and usability for users of PowerShell cmdlets and functions. + +## Parameter Naming Rules + +### General Conventions +- **Singular Nouns**: Use singular nouns for parameter names even if the parameter is expected to handle multiple values (e.g., `File` instead of `Files`). +- **Use PascalCase**: Parameter names must use PascalCase (e.g., `ParameterName`). +- **Descriptive Names**: Parameter names should be descriptive and convey their purpose clearly (e.g., `FilePath`, `UserName`). +- **Avoid Abbreviations**: Avoid using abbreviations unless they are widely recognized (e.g., `ID` for Identifier). +- **Avoid Reserved Words**: Do not use PowerShell reserved words as parameter names (e.g., `if`, `else`, `function`). + +### Units and Precision +- **Include Units in Parameter Names**: When a parameter represents a value with units, include the unit in the parameter name for clarity: + - `TimeoutSec` instead of `Timeout` + - `RetryIntervalSec` instead of `RetryInterval` + - `MaxSizeBytes` instead of `MaxSize` +- **Use Full Words for Clarity**: Spell out common terms to match PowerShell conventions: + - `MaximumRetryCount` instead of `MaxRetries` + - `MinimumLength` instead of `MinLength` + +### Alignment with Built-in Cmdlets +- **Follow Existing PowerShell Conventions**: When your parameter serves a similar purpose to a built-in cmdlet parameter, use the same or similar naming: + - Match `Invoke-WebRequest` parameters when making HTTP requests: `TimeoutSec`, `MaximumRetryCount`, `RetryIntervalSec` + - Follow common parameter patterns like `Path`, `Force`, `Recurse`, `WhatIf`, `Confirm` +- **Consistency Within Scripts**: If multiple parameters relate to the same concept, use consistent naming patterns (e.g., `TimeoutSec`, `RetryIntervalSec` both use `Sec` suffix). + +## Examples + +### Good Parameter Names +```powershell +param( + [string[]]$File, # Singular, even though it accepts arrays + [int]$TimeoutSec = 30, # Unit included + [int]$MaximumRetryCount = 2, # Full word "Maximum" + [int]$RetryIntervalSec = 2, # Consistent with TimeoutSec + [string]$Path, # Standard PowerShell convention + [switch]$Force # Common PowerShell parameter +) +``` + +### Names to Avoid +```powershell +param( + [string[]]$Files, # Should be singular: File + [int]$Timeout = 30, # Missing unit: TimeoutSec + [int]$MaxRetries = 2, # Should be: MaximumRetryCount + [int]$RetryInterval = 2, # Missing unit: RetryIntervalSec + [string]$FileLoc, # Avoid abbreviations: FilePath + [int]$Max # Ambiguous: MaximumWhat? +) +``` + +## Exceptions +- **Common Terms**: Some common terms may be used in plural form if they are widely accepted in the context (e.g., `Credentials`, `Permissions`). +- **Legacy Code**: Existing code that does not follow these conventions may be exempted to avoid breaking changes, but new code should adhere to these guidelines. +- **Well Established Naming Patterns**: If a naming pattern is well established in the PowerShell community, it may be used even if it does not strictly adhere to these guidelines. + +## References +- [PowerShell Cmdlet Design Guidelines](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) +- [About Parameters - PowerShell Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_parameters) diff --git a/.github/instructions/publishing-pester-result.instructions.md b/.github/instructions/publishing-pester-result.instructions.md new file mode 100644 index 00000000000..49010e65a99 --- /dev/null +++ b/.github/instructions/publishing-pester-result.instructions.md @@ -0,0 +1,272 @@ +--- +applyTo: ".github/**/*.{yml,yaml}" +--- + +# Publishing Pester Test Results Instructions + +This document describes how the PowerShell repository uses GitHub Actions to publish Pester test results. + +## Overview + +The PowerShell repository uses a custom composite GitHub Action located at `.github/actions/test/process-pester-results` to process and publish Pester test results in CI/CD workflows. +This action aggregates test results from NUnitXml formatted files, creates a summary in the GitHub Actions job summary, and uploads the results as artifacts. + +## How It Works + +### Action Location and Structure + +**Path**: `.github/actions/test/process-pester-results/` + +The action consists of two main files: + +1. **action.yml** - The composite action definition +1. **process-pester-results.ps1** - PowerShell script that processes test results + +### Action Inputs + +The action accepts the following inputs: + +- **name** (required): A descriptive name for the test run (e.g., "UnelevatedPesterTests-CI") + - Used for naming the uploaded artifact and in the summary + - Format: `junit-pester-{name}` + +- **testResultsFolder** (optional): Path to the folder containing test result XML files + - Default: `${{ runner.workspace }}/testResults` + - The script searches for all `*.xml` files in this folder recursively + +### Action Workflow + +The action performs the following steps: + +1. **Process Test Results** + - Runs `process-pester-results.ps1` with the provided name and test results folder + - Parses all NUnitXml formatted test result files (`*.xml`) + - Aggregates test statistics across all files: + - Total test cases + - Errors + - Failures + - Not run tests + - Inconclusive tests + - Ignored tests + - Skipped tests + - Invalid tests + +1. **Generate Summary** + - Creates a markdown summary using the `$GITHUB_STEP_SUMMARY` environment variable + - Uses `Write-Log` and `Write-LogGroupStart`/`Write-LogGroupEnd` functions from `build.psm1` + - Outputs a formatted summary with all test statistics + - Example format: + + ```markdown + # Summary of {Name} + + - Total Tests: X + - Total Errors: X + - Total Failures: X + - Total Not Run: X + - Total Inconclusive: X + - Total Ignored: X + - Total Skipped: X + - Total Invalid: X + ``` + +1. **Upload Artifacts** + - Uses `actions/upload-artifact@v4` to upload test results + - Artifact name: `junit-pester-{name}` + - Always runs (even if previous steps fail) via `if: always()` + - Uploads the entire test results folder + +1. **Exit Status** + - Fails the job (exit 1) if: + - Any test errors occurred (`$testErrorCount -gt 0`) + - Any test failures occurred (`$testFailureCount -gt 0`) + - No test cases were run (`$testCaseCount -eq 0`) + +## Usage in Test Actions + +The `process-pester-results` action is called by two platform-specific composite test actions: + +### Linux/macOS Tests: `.github/actions/test/nix` + +Used in: + +- `.github/workflows/linux-ci.yml` +- `.github/workflows/macos-ci.yml` + +Example usage (lines 99-104 in `nix/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: "${{ runner.workspace }}/testResults" +``` + +### Windows Tests: `.github/actions/test/windows` + +Used in: + +- `.github/workflows/windows-ci.yml` + +Example usage (line 78-83 in `windows/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: ${{ runner.workspace }}\testResults +``` + +## Workflow Integration + +The process-pester-results action is integrated into the CI workflows through a multi-level hierarchy: + +### Level 1: Main CI Workflows + +- `linux-ci.yml` +- `macos-ci.yml` +- `windows-ci.yml` + +### Level 2: Test Jobs + +Each workflow contains multiple test jobs with different purposes and tag sets: + +- `UnelevatedPesterTests` with tagSet `CI` +- `ElevatedPesterTests` with tagSet `CI` +- `UnelevatedPesterTests` with tagSet `Others` +- `ElevatedPesterTests` with tagSet `Others` + +### Level 3: Platform Test Actions + +Test jobs use platform-specific actions: + +- `nix` for Linux and macOS +- `windows` for Windows + +### Level 4: Process Results Action + +Platform actions call `process-pester-results` to publish results + +## Test Execution Flow + +1. **Build Phase**: Source code is built (e.g., in `ci_build` job) +1. **Test Preparation**: + - Build artifacts are downloaded + - PowerShell is bootstrapped + - Test binaries are extracted +1. **Test Execution**: + - `Invoke-CITest` is called with: + - `-Purpose`: Test purpose (e.g., "UnelevatedPesterTests") + - `-TagSet`: Test category (e.g., "CI", "Others") + - `-OutputFormat NUnitXml`: Results format + - Results are written to `${{ runner.workspace }}/testResults` +1. **Results Processing**: + - `process-pester-results` action runs + - Results are aggregated and summarized + - Artifacts are uploaded + - Job fails if any tests failed or errored + +## Key Dependencies + +### PowerShell Modules + +- **build.psm1**: Provides utility functions + - `Write-Log`: Logging function with GitHub Actions support + - `Write-LogGroupStart`: Creates collapsible log groups + - `Write-LogGroupEnd`: Closes collapsible log groups + +### GitHub Actions Features + +- **GITHUB_STEP_SUMMARY**: Environment variable for job summary +- **actions/upload-artifact@v4**: For uploading test results +- **Composite Actions**: For reusable workflow steps + +### Test Result Format + +- **NUnitXml**: XML format for test results +- Expected XML structure with `test-results` root element containing: + - `total`: Total number of tests + - `errors`: Number of errors + - `failures`: Number of failures + - `not-run`: Number of tests not run + - `inconclusive`: Number of inconclusive tests + - `ignored`: Number of ignored tests + - `skipped`: Number of skipped tests + - `invalid`: Number of invalid tests + +## Best Practices + +1. **Naming Convention**: Use descriptive names that include both purpose and tagSet: + - Format: `{purpose}-{tagSet}` + - Example: `UnelevatedPesterTests-CI` + +1. **Test Results Location**: + - Default location: `${{ runner.workspace }}/testResults` + - Use platform-appropriate path separators (Windows: `\`, Unix: `/`) + +1. **Always Upload**: The artifact upload step uses `if: always()` to ensure results are uploaded even when tests fail + +1. **Error Handling**: The action will fail the job if: + - Tests have errors or failures (intentional fail-fast behavior) + - No tests were executed (potential configuration issue) + - `GITHUB_STEP_SUMMARY` is not set (environment issue) + +## Customizing for Your Repository + +To use this pattern in another repository: + +1. **Copy the Action Files**: + - Copy `.github/actions/test/process-pester-results/` directory + - Ensure the PowerShell script has proper permissions + +1. **Adjust Dependencies**: + - Modify or remove the `Import-Module "$PSScriptRoot/../../../../build.psm1"` line + - Implement equivalent `Write-Log` and `Write-LogGroup*` functions if needed + +1. **Customize Summary Format**: + - Modify the here-string in `process-pester-results.ps1` to change summary format + - Add additional metrics or formatting as needed + +1. **Call from Your Workflows**: + + ```yaml + - name: Process Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "my-test-run" + testResultsFolder: "path/to/results" + ``` + +## Related Documentation + +- [GitHub Actions: Creating composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) +- [GitHub Actions: Job summaries](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary) +- [GitHub Actions: Uploading artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) +- [Pester: PowerShell testing framework](https://pester.dev/) +- [NUnit XML Format](https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html) + +## Troubleshooting + +### No Test Results Found + +- Verify `testResultsFolder` path is correct +- Ensure tests are generating NUnitXml formatted output +- Check that `*.xml` files exist in the specified folder + +### Action Fails with "GITHUB_STEP_SUMMARY is not set" + +- Ensure the action runs within a GitHub Actions environment +- Cannot be run locally without mocking this environment variable + +### All Tests Pass but Job Fails + +- Check if any tests are marked as errors (different from failures) +- Verify that at least some tests executed (`$testCaseCount -eq 0`) + +### Artifact Upload Fails + +- Check artifact name for invalid characters +- Ensure the test results folder exists +- Verify actions/upload-artifact version compatibility diff --git a/.github/instructions/script-module-file-format.instructions.md b/.github/instructions/script-module-file-format.instructions.md new file mode 100644 index 00000000000..922e0d4aa31 --- /dev/null +++ b/.github/instructions/script-module-file-format.instructions.md @@ -0,0 +1,27 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# Script and Module File Format + +These instructions define required file-level formatting for PowerShell scripts and module files in this repository. + +## Copyright Header + +If a change adds a new `.ps1` file or `.psm1` file or touches an existing one, the file should start with the copyright and license header and have an empty line after it, as shown in the example below: + +```powershell +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +``` + +Do not place blank lines, comments, or code before this header. + +## Requirements + +- Add the copyright header when creating a new `.ps1` or `.psm1` file. +- Preserve the header when editing an existing `.ps1` or `.psm1` file. +- If an existing `.ps1` or `.psm1` file is missing the header, only modify that file to add the header if a change touches that file. Do not make a change to add the header if the file is not being modified. diff --git a/.github/instructions/start-native-execution.instructions.md b/.github/instructions/start-native-execution.instructions.md new file mode 100644 index 00000000000..347e496b3bf --- /dev/null +++ b/.github/instructions/start-native-execution.instructions.md @@ -0,0 +1,149 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# Using Start-NativeExecution for Native Command Execution + +## Purpose + +`Start-NativeExecution` is the standard function for executing native commands (external executables) in PowerShell scripts within this repository. It provides consistent error handling and better diagnostics when native commands fail. + +## When to Use + +Use `Start-NativeExecution` whenever you need to: +- Execute external commands (e.g., `git`, `dotnet`, `pkgbuild`, `productbuild`, `fpm`, `rpmbuild`) +- Ensure proper exit code checking +- Get better error messages with caller information +- Handle verbose output on error + +## Basic Usage + +```powershell +Start-NativeExecution { + git clone https://github.com/PowerShell/PowerShell.git +} +``` + +## With Parameters + +Use backticks for line continuation within the script block: + +```powershell +Start-NativeExecution { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + $outputPath +} +``` + +## Common Parameters + +### -VerboseOutputOnError + +Captures command output and displays it only if the command fails: + +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet build --configuration Release +} +``` + +### -IgnoreExitcode + +Allows the command to fail without throwing an exception: + +```powershell +Start-NativeExecution -IgnoreExitcode { + git diff --exit-code # Returns 1 if differences exist +} +``` + +## Availability + +The function is defined in `tools/buildCommon/startNativeExecution.ps1` and is available in: +- `build.psm1` (dot-sourced automatically) +- `tools/packaging/packaging.psm1` (dot-sourced automatically) +- Test modules that include `HelpersCommon.psm1` + +To use in other scripts, dot-source the function: + +```powershell +. "$PSScriptRoot/../buildCommon/startNativeExecution.ps1" +``` + +## Error Handling + +When a native command fails (non-zero exit code), `Start-NativeExecution`: +1. Captures the exit code +2. Identifies the calling location (file and line number) +3. Throws a descriptive error with full context + +Example error message: +``` +Execution of {git clone ...} by /path/to/script.ps1: line 42 failed with exit code 1 +``` + +## Examples from the Codebase + +### Git Operations +```powershell +Start-NativeExecution { + git fetch --tags --quiet upstream +} +``` + +### Build Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet publish --configuration Release +} +``` + +### Packaging Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot --identifier $pkgId --version $version $outputPath +} +``` + +### Permission Changes +```powershell +Start-NativeExecution { + find $staging -type d | xargs chmod 755 + find $staging -type f | xargs chmod 644 +} +``` + +## Anti-Patterns + +**Don't do this:** +```powershell +& somecommand $args +if ($LASTEXITCODE -ne 0) { + throw "Command failed" +} +``` + +**Do this instead:** +```powershell +Start-NativeExecution { + somecommand $args +} +``` + +## Best Practices + +1. **Always use Start-NativeExecution** for native commands to ensure consistent error handling +2. **Use -VerboseOutputOnError** for commands with useful diagnostic output +3. **Use backticks for readability** when commands have multiple arguments +4. **Don't capture output unnecessarily** - let the function handle it +5. **Use -IgnoreExitcode sparingly** - only when non-zero exit codes are expected and acceptable + +## Related Documentation + +- Source: `tools/buildCommon/startNativeExecution.ps1` +- Blog post: https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ diff --git a/.github/instructions/start-psbuild-basics.instructions.md b/.github/instructions/start-psbuild-basics.instructions.md new file mode 100644 index 00000000000..18a0026eb2d --- /dev/null +++ b/.github/instructions/start-psbuild-basics.instructions.md @@ -0,0 +1,100 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Start-PSBuild Basics + +## Purpose + +`Start-PSBuild` builds PowerShell from source. It's defined in `build.psm1` and used in CI/CD workflows. + +## Default Usage + +For most scenarios, use with no parameters: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` + +**Default behavior:** +- Configuration: `Debug` +- PSModuleRestore: Enabled +- Runtime: Auto-detected for platform + +## Common Configurations + +### Debug Build (Default) + +```powershell +Start-PSBuild +``` + +Use for: +- Testing (xUnit, Pester) +- Development +- Debugging + +### Release Build + +```powershell +Start-PSBuild -Configuration 'Release' +``` + +Use for: +- Production packages +- Distribution +- Performance testing + +### Code Coverage Build + +```powershell +Start-PSBuild -Configuration 'CodeCoverage' +``` + +Use for: +- Code coverage analysis +- Test coverage reports + +## Common Parameters + +### -Configuration + +Values: `Debug`, `Release`, `CodeCoverage`, `StaticAnalysis` + +Default: `Debug` + +### -CI + +Restores Pester module for CI environments. + +```powershell +Start-PSBuild -CI +``` + +### -PSModuleRestore + +Now enabled by default. Use `-NoPSModuleRestore` to skip. + +### -ReleaseTag + +Specifies version tag for release builds: + +```powershell +$releaseTag = Get-ReleaseTag +Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +## Workflow Example + +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` diff --git a/.github/instructions/troubleshooting-builds.instructions.md b/.github/instructions/troubleshooting-builds.instructions.md new file mode 100644 index 00000000000..e9b60cb8c80 --- /dev/null +++ b/.github/instructions/troubleshooting-builds.instructions.md @@ -0,0 +1,100 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Troubleshooting Build Issues + +## Git Describe Error + +**Error:** +``` +error MSB3073: The command "git describe --abbrev=60 --long" exited with code 128. +``` + +**Cause:** Insufficient git history (shallow clone) + +**Solution:** Add `fetch-depth: 1000` to checkout step + +```yaml +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 +``` + +## Version Information Incorrect + +**Symptom:** Build produces wrong version numbers + +**Cause:** Git tags not synchronized + +**Solution:** Run `Sync-PSTags -AddRemoteIfMissing`: + +```yaml +- name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing +``` + +## PowerShell Binary Not Built + +**Error:** +``` +Exception: CoreCLR pwsh.exe was not built +``` + +**Causes:** +1. Build failed (check logs) +2. Wrong configuration used +3. Build output location incorrect + +**Solutions:** +1. Check build logs for errors +2. Verify correct configuration for use case +3. Use default parameters: `Start-PSBuild` + +## Module Restore Issues + +**Symptom:** Slow build or module restore failures + +**Causes:** +- Network issues +- Module cache problems +- Package source unavailable + +**Solutions:** +1. Retry the build +2. Check network connectivity +3. Use `-NoPSModuleRestore` if modules not needed +4. Clear package cache if persistent + +## .NET SDK Not Found + +**Symptom:** Build can't find .NET SDK + +**Solution:** Ensure .NET setup step runs first: + +```yaml +- name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json +``` + +## Bootstrap Failures + +**Symptom:** Invoke-CIInstall fails + +**Causes:** +- Missing dependencies +- Network issues +- Platform-specific requirements not met + +**Solution:** Check prerequisites for your platform in build system docs diff --git a/.github/policies/IssueManagement.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.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/policies/labelAdded.approvedLowRisk.yml b/.github/policies/labelAdded.approvedLowRisk.yml new file mode 100644 index 00000000000..bdeea5265a0 --- /dev/null +++ b/.github/policies/labelAdded.approvedLowRisk.yml @@ -0,0 +1,48 @@ +id: labelAdded.approvedLowRisk +name: GitOps.PullRequestIssueManagement +description: Remove Approved-LowRisk if applied by an unauthorized user +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Remove Approved-LowRisk if label was added by someone not authorized + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: Approved-LowRisk + # Unauthorized = NOT admin AND NOT in explicit allowlist + - not: + or: + - activitySenderHasPermission: + permission: Admin + + # Allowlist (enabled) + - isActivitySender: + user: iSazonov + issueAuthor: False + - isActivitySender: + user: daxian-dbw + issueAuthor: False + + # Allowlist (commented out for now) + # - isActivitySender: + # user: TravisEz13 + # issueAuthor: False + # - isActivitySender: + # user: adityapatwardhan + # issueAuthor: False + # - isActivitySender: + # user: jshigetomi + # issueAuthor: False + then: + - removeLabel: + label: Approved-LowRisk + - addReply: + reply: >- + The `Approved-LowRisk` label is restricted to authorized maintainers and was removed. +onFailure: +onSuccess: diff --git a/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml new file mode 100644 index 00000000000..78edc18cb1a --- /dev/null +++ b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml @@ -0,0 +1,56 @@ +id: labelAdded.clBuildPackaging.addBackportConsider +name: GitOps.PullRequestIssueManagement +description: Add backport consideration labels when CL-BuildPackaging is added to an open PR targeting master +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Add BackPort-7.4.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.4.x-Consider + then: + - addLabel: + label: BackPort-7.4.x-Consider + + - description: Add BackPort-7.5.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.5.x-Consider + then: + - addLabel: + label: BackPort-7.5.x-Consider + + - description: Add BackPort-7.6.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.6.x-Consider + then: + - addLabel: + label: BackPort-7.6.x-Consider +onFailure: +onSuccess: diff --git a/.github/prompts/backport-pr-to-release-branch.prompt.md b/.github/prompts/backport-pr-to-release-branch.prompt.md new file mode 100644 index 00000000000..32bff10bd5e --- /dev/null +++ b/.github/prompts/backport-pr-to-release-branch.prompt.md @@ -0,0 +1,567 @@ +--- +description: Guide for backporting changes to PowerShell release branches +--- + +# Backport a Change to a PowerShell Release Branch + +## 1 — Goal + +Create a backport PR that applies changes from a merged PR to a release branch (e.g., `release/v7.4`, `release/v7.5`). The backport must follow the repository's established format and include proper references to the original PR. + +## 2 — Prerequisites for the model + +- You have full repository access +- You can run git commands +- You can read PR information from the repository +- Ask clarifying questions if the target release branch or original PR number is unclear + +## 3 — Required user inputs + +If the user hasn't specified a PR number, help them find one: + +### Finding PRs that need backporting + +1. Ask the user which release version they want to backport to (e.g., `7.4`, `7.5`) +2. Search for PRs with the appropriate label using GitHub CLI: + +```powershell +$Owner = "PowerShell" +$Repo = "PowerShell" +$version = "7.4" # or user-specified version +$considerLabel = "Backport-$version.x-Consider" + +$prsJson = gh pr list --repo "$Owner/$Repo" --label $considerLabel --state merged --json number,title,url,labels,mergedAt --limit 100 2>&1 +$prs = $prsJson | ConvertFrom-Json +# Sort PRs from oldest merged to newest merged +$prs = $prs | Sort-Object mergedAt +``` + +3. Present the list of PRs to the user with: + - PR number + - PR title + - Merged date + - URL + +4. Ask the user: "Which PR would you like to backport?" (provide the PR number) + +### After selecting a PR + +Once the user selects a PR (or if they provided one initially), confirm: +- **Original PR number**: The PR number that was merged to the main branch (e.g., 26193) +- **Target release**: The release number (e.g., `7.4`, `7.5`, `7.5.1`) + +Example: "Backport PR 26193 to release/v7.4" + +## 4 — Implementation steps (must be completed in order) + +### Step 1: Verify the original PR exists and is merged + +1. Fetch the original PR information using the PR number +2. Confirm the PR state is `MERGED` +3. Extract the following information: + - Merge commit SHA + - Original PR title + - Original PR author + - Original CL label (if present, typically starts with `CL-`) + +If the PR is not merged, stop and inform the user. + +4. Check if backport already exists or has been attempted: + ```powershell + gh pr list --repo PowerShell/PowerShell --search "in:title [release/v7.4] " --state all + ``` + + If a backport PR already exists, inform the user and ask if they want to continue. + +5. Check backport labels to understand status: + - `Backport-7.4.x-Migrated`: Indicates previous backport attempt (may have failed or had issues) + - `Backport-7.4.x-Done`: Already backported successfully + - `Backport-7.4.x-Approved`: Ready for backporting + - `Backport-7.4.x-Consider`: Under consideration for backporting + + If status is "Done", inform the user that backport may already be complete. + +### Step 2: Create the backport branch + +1. Identify the correct remote to fetch from: + ```bash + git remote -v + ``` + + Look for the remote that points to `https://github.com/PowerShell/PowerShell` (typically named `upstream` or `origin`). Use this remote name in subsequent commands. + +2. Ensure you have the latest changes from the target release branch: + ```bash + git fetch + ``` + + Example: `git fetch upstream release/v7.4` + +3. Create a new branch from the target release branch: + ```bash + git checkout -b backport- / + ``` + + Example: `git checkout -b backport-26193 upstream/release/v7.4` + +### Step 3: Cherry-pick the merge commit + +1. Cherry-pick the merge commit from the original PR: + ```bash + git cherry-pick + ``` + +2. If conflicts occur: + - Inform the user about the conflicts + - List the conflicting files + - Fetch the original PR diff to understand the changes: + ```bash + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + - Review the diff to understand what the PR changed + - Figure out why there is a conflict and resolve it + - Create a summary of the conflict resolution: + * Which files had conflicts + * Nature of each conflict (parameter changes, code removal, etc.) + * How you resolved it + * Whether any manual adjustments were needed beyond accepting one side + - Ask the user to review your conflict resolution summary before continuing + - After conflicts are resolved, continue with: + ```bash + git add + git cherry-pick --continue + ``` + +### Step 4: Push the backport branch + +Push to your fork (typically the remote that you have write access to): + +```bash +git push backport- +``` + +Example: `git push origin backport-26193` + +Note: If you're pushing to the official PowerShell repository and have permissions, you may push to `upstream` or the appropriate remote. + +### Step 5: Create the backport PR + +Create a new PR with the following format: + +**Title:** +``` +[] +``` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +**Body:** +``` +Backport of # to + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers + +## Impact + +Choose either tooling or Customer impact. +### Tooling Impact + +- [ ] Required tooling change +- [ ] Optional tooling change (include reasoning) + +### Customer Impact + +- [ ] Customer reported +- [ ] Found internally + +[Select one or both of the boxes. Describe how this issue impacts customers, citing the expected and actual behaviors and scope of the issue. If customer-reported, provide the issue number.] + +## Regression + +- [ ] Yes +- [ ] No + +[If yes, specify when the regression was introduced. Provide the PR or commit if known.] + +## Testing + +[How was the fix verified? How was the issue missed previously? What tests were added?] + +## Risk + +- [ ] High +- [ ] Medium +- [ ] Low + +[High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.] +``` + +**Base branch:** `` (e.g., `release/v7.4`) + +**Head branch:** `backport-` (e.g., `backport-26193`) + +#### Guidelines for Filling Out the PR Body + +**For Impact Section**: +- If the original PR changed build/tooling/packaging, select "Tooling Impact" +- If it fixes a user-facing bug or changes user-visible behavior, select "Customer Impact" +- Copy relevant context from the original PR description +- Be specific about what changed and why + +**For Regression Section**: +- Mark "Yes" only if the original PR fixed a regression +- Include when the regression was introduced if known + +**For Testing Section**: +- Reference the original PR's testing approach +- Note any additional backport-specific testing needed +- Mention if manual testing was done to verify the backport + +**For Risk Assessment**: +- **High**: Changes core functionality, packaging, build systems, or security-related code +- **Medium**: Changes non-critical features, adds new functionality, or modifies existing behavior +- **Low**: Documentation, test-only changes, minor refactoring, or fixes with narrow scope +- Justify your assessment based on the scope of changes and potential impact +- **For CI/CD changes**: When backporting CI/CD infrastructure changes (workflows, build scripts, packaging), note in your justification that not taking these changes may create technical debt and make it difficult to apply future CI/CD changes that build on top of them. This doesn't change the risk level itself, but provides important context for why the change should be taken despite potentially higher risk + +**If there were merge conflicts**: +Add a note in the PR description after the Risk section describing what conflicts occurred and how they were resolved. + +### Step 6: Add the CL label to the backport PR + +After creating the backport PR, add the same changelog label (CL-*) from the original PR to the backport PR: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "" +``` + +Example: `gh pr edit 26389 --repo PowerShell/PowerShell --add-label "CL-BuildPackaging"` + +This ensures the backport is properly categorized in the changelog for the release branch. + +### Step 7: Update the original PR's backport labels + +After successfully creating the backport PR, update the original PR to reflect that it has been backported: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "Backport-.x-Migrated" --remove-label "Backport-.x-Consider" +``` + +Example: `gh pr edit 26193 --repo PowerShell/PowerShell --add-label "Backport-7.5.x-Migrated" --remove-label "Backport-7.5.x-Consider"` + +Notes: +- If the original PR had `Backport-.x-Approved` instead of `Consider`, remove that label +- This step helps track which PRs have been successfully backported +- The `Migrated` label indicates the backport PR has been created (not necessarily merged) +- The `Done` label should only be added once the backport PR is merged + +### Step 8: Clean up temporary files + +After successful PR creation and labeling, clean up any temporary files created during the process: + +```powershell +Remove-Item pr*.diff -ErrorAction SilentlyContinue +``` + +## 5 — Definition of Done (self-check list) + +- [ ] Original PR is verified as merged +- [ ] Checked for existing backport PRs +- [ ] Reviewed backport labels to understand status +- [ ] Backport branch created from correct release branch +- [ ] Merge commit cherry-picked successfully (or conflicts resolved) +- [ ] If conflicts occurred, provided resolution summary to user +- [ ] Branch pushed to origin +- [ ] PR created with correct title format: `[] ` +- [ ] CL label added to backport PR (matching original PR's CL label) +- [ ] Original PR labels updated (added Migrated, removed Consider/Approved) +- [ ] Temporary files cleaned up (pr*.diff) +- [ ] PR body includes: + - [ ] Backport reference: `Backport of (PR-number) to ` + - [ ] Auto-generated comment with original PR number + - [ ] Triggered by and original author attribution + - [ ] Original CL label (if available) + - [ ] CC to PowerShell maintainers + - [ ] Impact section filled out + - [ ] Regression section filled out + - [ ] Testing section filled out + - [ ] Risk section filled out +- [ ] Base branch set to target release branch +- [ ] No unrelated changes included + +## 6 — Branch naming convention + +**Format:** `backport/release//pr/` + +Examples: +- `backport/release/v7.5/pr/26193` +- `backport/release/v7.4.1/pr/26334` + +Note: Automated bot uses format `backport/release/v/-`, but manual backports should use the format `backport/release//pr/` as shown above. + +## 7 — Example backport PR + +Reference PR 26334 as the canonical example of a correct backport: + +**Original PR**: PR 26193 "GitHub Workflow cleanup" + +**Backport PR**: PR 26334 "[release/v7.4] GitHub Workflow cleanup" +- **Title**: `[release/v7.4] GitHub Workflow cleanup` +- **Body**: Started with backport reference to original PR and release branch +- **Branch**: `backport/release/v7.4/26193-4aff02475` (bot-created) +- **Base**: `release/v7.4` +- **Includes**: Auto-generated metadata, impact assessment, regression info, testing details, and risk level + +## 8 — Backport label system (for context) + +Backport labels follow pattern: `Backport-.x-` + +**Triage states:** +- `Consider` - Under review for backporting +- `Approved` - Approved and ready to be backported +- `Done` - Backport completed + +**Examples:** `Backport-7.4.x-Approved`, `Backport-7.5.x-Consider`, `Backport-7.3.x-Done` + +Note: The PowerShell repository has an automated bot (pwshBot) that creates backport PRs automatically when a merged PR has a backport approval label. Manual backports follow the same format. + +## Manual Backport Using PowerShell Tools + +For situations where automated backports fail or manual intervention is needed, use the `Invoke-PRBackport` function from `tools/releaseTools.psm1`. + +### Prerequisites + +1. **GitHub CLI**: Install from https://cli.github.com/ + - Required version: 2.17 or later + - Authenticate with `gh auth login` + +2. **Upstream Remote**: Configure a Git remote named `upstream` pointing to `PowerShell/PowerShell`: + ```powershell + git remote add upstream https://github.com/PowerShell/PowerShell.git + ``` + +### Using Invoke-PRBackport + +```powershell +# Import the release tools module +Import-Module ./tools/releaseTools.psm1 + +# Backport a single PR +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 + +# Backport with custom branch postfix +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -BranchPostFix "retry" + +# Overwrite existing local branch if it exists +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -Overwrite +``` + +### Parameters + +- **PrNumber** (Required): The PR number to backport +- **Target** (Required): Target release branch (must match pattern `release/v\d+\.\d+(\.\d+)?`) +- **Overwrite**: Switch to overwrite local branch if it already exists +- **BranchPostFix**: Add a postfix to the branch name (e.g., for retry attempts) +- **UpstreamRemote**: Name of the upstream remote (default: `upstream`) + +### How It Works + +1. Verifies the PR is merged +2. Fetches the target release branch from upstream +3. Creates a new branch: `backport-[-]` +4. Cherry-picks the merge commit +5. If conflicts occur, prompts you to resolve them +6. Creates the backport PR using GitHub CLI + +## Handling Merge Conflicts + +When cherry-picking fails due to conflicts: + +1. The script will pause and prompt you to fix conflicts +2. Resolve conflicts in your editor: + ```powershell + # Check which files have conflicts + git status + + # Edit files to resolve conflicts + # After resolving, stage the changes + git add + + # Continue the cherry-pick + git cherry-pick --continue + ``` +3. Type 'Yes' when prompted to continue the script +4. The script will create the PR + +### Understanding Conflict Patterns + +When resolving conflicts during backports, follow this approach: + +1. **Analyze the diff first**: Before resolving conflicts, fetch and review the original PR's diff to understand what changed: + ```powershell + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + +2. **Identify conflict types**: + - **Parameter additions**: New parameters added to functions (e.g., ValidateSet values) + - **Code removal**: Features removed in main but still exist in release branch + - **Code additions**: New code blocks that don't exist in release branch + - **Refactoring conflicts**: Code structure changes between branches + +3. **Resolution priorities**: + - Preserve the intent of the backported change + - Keep release branch-specific code that doesn't conflict with the fix + - When in doubt, favor the incoming change from the backport + - Document significant manual changes in the PR description + +4. **Verification**: + - After resolving conflicts, verify the file compiles/runs + - Check that the resolved code matches the original PR's intent + - Look for orphaned code that references removed functions + +5. **Create a conflict resolution summary**: + - List which files had conflicts + - Briefly explain the nature of each conflict + - Describe how you resolved it + - Ask user to review the resolution before continuing + +### Context-Aware Conflict Resolution + +**Key Principle**: The release branch may have different code than main. Your goal is to apply the *change* from the PR, not necessarily make the code identical to main. + +**Common Scenarios**: +1. **Function parameters differ**: If the release branch has fewer parameters than main, and the backport adds functionality unrelated to new parameters, keep the release branch parameters unless the new parameters are part of the fix +2. **Dependencies removed in main**: If main removed a dependency but the release branch still has it, and the backport is unrelated to that dependency, keep the release branch code +3. **New features in main**: If main has new features not in the release, focus on backporting only the specific fix, not the new features + +## Bulk Backporting Approved PRs + +To backport all PRs labeled as approved for a specific version: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# Backport all approved PRs for version 7.2.12 +Invoke-PRBackportApproved -Version 7.2.12 +``` + +This function: +1. Queries all merged PRs with the `Backport-.x-Approved` label +2. Attempts to backport each PR in order of merge date +3. Creates individual backport PRs for each + +## Viewing Backport Reports + +Get a list of PRs that need backporting: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# List all approved backports for 7.4 +Get-PRBackportReport -Version 7.4 -TriageState Approved + +# Open all approved backports in browser +Get-PRBackportReport -Version 7.4 -TriageState Approved -Web + +# Check which backports are done +Get-PRBackportReport -Version 7.4 -TriageState Done +``` + +## Branch Naming Conventions + +### Automated Bot Branches +Format: `backport/release/v/-` + +Example: `backport/release/v7.4/26193-4aff02475` + +### Manual Backport Branches +Format: `backport-[-]` + +Examples: +- `backport-26193` +- `backport-26193-retry` + +## PR Title and Description Format + +### Title +Format: `[release/v] ` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +### Description +The backport PR description includes: +- Reference to original PR number +- Target release branch +- Auto-generated comment with original PR metadata +- Maintainer information +- Original CL label +- CC to PowerShell maintainers team + +Example description structure: +```text +Backport of (original-pr-number) to release/v + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers +``` + +## Best Practices + +1. **Verify PR is merged**: Only backport merged PRs +2. **Test backports**: Ensure backported changes work in the target release context +3. **Check for conflicts early**: Large PRs are more likely to have conflicts +4. **Use appropriate labels**: Apply correct version and triage state labels +5. **Document special cases**: If manual changes were needed, note them in the PR description +6. **Follow up on CI failures**: Backports should pass all CI checks before merging + +## Troubleshooting + +### "PR is not merged" Error +**Cause**: Attempting to backport a PR that hasn't been merged yet +**Solution**: Wait for the PR to be merged to the main branch first + +### "Please create an upstream remote" Error +**Cause**: No upstream remote configured +**Solution**: +```powershell +git remote add upstream https://github.com/PowerShell/PowerShell.git +git fetch upstream +``` + +### "GitHub CLI is not installed" Error +**Cause**: gh CLI not found in PATH +**Solution**: Install from https://cli.github.com/ and restart terminal + +### Cherry-pick Conflicts +**Cause**: Changes conflict with the target branch +**Solution**: Manually resolve conflicts, stage files, and continue cherry-pick + +### "Commit does not exist" Error +**Cause**: Local Git doesn't have the commit +**Solution**: +```powershell +git fetch upstream +``` + +## Related Resources + +- **Release Process**: See `docs/maintainers/releasing.md` +- **Release Tools**: See `tools/releaseTools.psm1` +- **Issue Management**: See `docs/maintainers/issue-management.md` diff --git a/.github/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/skills/analyze-pester-failures/SKILL.md b/.github/skills/analyze-pester-failures/SKILL.md new file mode 100644 index 00000000000..ec1b0fe82ec --- /dev/null +++ b/.github/skills/analyze-pester-failures/SKILL.md @@ -0,0 +1,524 @@ +--- +name: analyze-pester-failures +description: Troubleshooting guide for analyzing and investigating Pester test failures in PowerShell CI jobs. Help agents understand why tests are failing, interpret test output, navigate test result artifacts, and provide actionable recommendations for fixing test issues. +--- + +# Analyze Pester Test Failures + +Investigate and troubleshoot Pester test failures in GitHub Actions workflows. Understand what tests are failing, why they're failing, and provide recommendations for test fixes. + +| Skill | When to Use | +|-------|-----------| +| analyze-pester-failures | When investigating why Pester tests are failing in a CI job. Use when a test job shows failures and you need to understand what test failed, why it failed, what the error message means, and what might need to be fixed. Also use when asked: "why did this test fail?", "what's the test error?", "test is broken", "test failure analysis", "debug test failure", or given test failure logs and stack traces. | + +## When to Use This Skill + +Use this skill when you need to: + +- Understand why a specific Pester test is failing +- Interpret test failure messages and error output +- Analyze test result data from CI workflow runs (XML, logs, stack traces) +- Identify the root cause of test failures (test logic, assertion failure, exception, timeout, skip/ignore reason) +- Provide recommendations for fixing failing tests +- Compare expected vs. actual test behavior +- Debug test environment issues (missing dependencies, configuration problems) +- Understand test skip/ignored/inconclusive status reasons + +**Do not use this skill for:** +- General PowerShell debugging unrelated to tests +- Test infrastructure/CI setup issues (except as they affect test failure interpretation) +- Performance analysis or benchmarking (that's a different investigation) + +## Quick Start + +### ⚠️ CRITICAL: The Workflow Must Be Followed IN ORDER + +This skill describes a **sequential 6-step analysis workflow**. Skipping steps or jumping around leads to **incomplete analysis and incorrect conclusions**. + +**The Problem**: It's easy to skip to Step 4 or 5 without doing Steps 1-2, resulting in missing data and bad conclusions. + +**The Solution**: Use the automated analysis script to enforce the workflow: + +```powershell +# Automatically runs Steps 1-6 in order, preventing skipping +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR + +# Example: +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR 26800 +``` + +This script: +1. ✓ Fetches PR status automatically +2. ✓ Downloads artifacts (can't skip, depends on Step 1) +3. ✓ Extracts failures (can't skip, depends on Step 2) +4. ✓ Analyzes error messages +5. ✓ Documents context +6. ✓ Generates recommendations + +**Only use the manual commands below if you fully understand the workflow.** + +### Manual Workflow (for reference) + +```powershell +# Step 1: Identify the failing job +gh pr view --json 'statusCheckRollup' | ConvertFrom-Json | Where-Object { $_.conclusion -eq 'FAILURE' } + +# Step 2: Download artifacts (extract RUN_ID from Step 1) +gh run download --dir ./artifacts +gh run view --log > test-logs.txt + +# Step 3-6: Extract, analyze, and interpret +# (See Analysis Workflow section below) +``` + +## Common Test Failure Analysis Approaches + +### 1. **Interpreting Assertion Failures** +The most common test failure is when an assertion doesn't match expectations. + +**Example:** +``` +Expected $true but got $false at /path/to/test.ps1:42 +Assertion failed: Should -Be "expected" but was "actual" +``` + +**How to analyze:** +- Read the assertion message: what was expected vs. what was actual? +- Check the test logic: is the expectation correct? +- Look for mock/stub issues: are dependencies configured correctly? +- Check parameter values: what inputs were passed to the function under test? + +### 2. **Exception Failures** +Tests fail when PowerShell throws an exception instead of successful completion. + +**Example:** +``` +Command: Write-Host $null +Error: Cannot bind argument to parameter 'Object' because it is null. +``` + +**How to analyze:** +- Read the exception message: what operation failed? +- Check the stack trace: where in the test or tested code did it throw? +- Verify preconditions: does the test setup provide required values/mocks? +- Look for environmental issues: missing modules, permissions, file system state? + +### 3. **Timeout Failures** +A test takes longer than the allowed timeout to complete. + +**Example:** +``` +Test 'Should complete in reasonable time' timed out after 30 seconds +``` + +**How to analyze:** +- Is the timeout appropriate for this test type? (network tests need more time) +- Is there an infinite loop in the test or tested code? +- Are there resource contention issues on the CI runner? +- Does the test hang waiting for something (file lock, network, process)? + +### 4. **Skip/Ignored Reason Analysis** +Tests marked as skipped or ignored provide clues about test environment. + +**Example:** +``` +Test marked [Skip("Only runs on Windows")] - running on Linux +Test marked [Ignore("Known issue #12345")] +``` + +**How to analyze:** +- Read the skip/ignore reason: is it still valid? +- Check if environment has changed: platform, module versions, etc. +- Verify issue status: is the known issue still open? Has it been fixed? +- Determine if skip should be removed or if test needs environment changes + +### 5. **Flaky/Intermittent Failures** +Tests that sometimes pass, sometimes fail indicate race conditions or environment sensitivity. + +**Example:** +- Test passes locally but fails on CI +- Test passes first run of suite, fails on second run +- Test passes on Windows but fails on Linux + +**How to analyze:** +- Look for timeout races: is timing involved in the test? +- Check for test isolation issues: does one test affect another? +- Verify environment differences: CI vs. local paths, permissions, versions +- Look for external dependencies: network calls, file I/O, process interactions + +## Key Artifacts and Locations + +| Item | Purpose | Location | +|------|---------|----------| +| Test result XML | Pester output with test cases, failures, errors | Workflow artifacts: `junit-pester-*.xml` | +| Job logs | Full job output including test execution and errors | GitHub Actions run logs or `gh run download` | +| Stack traces | Error location information from failed assertions | Within job logs and XML failure messages | +| Test files | The actual Pester test code (`.ps1` files) | `test/` directory in repository | + +## Analysis Workflow + +### ⚠️ Important: These Steps MUST Be Followed In Order + +Each step depends on the previous one. Skipping or re-ordering steps causes incomplete analysis: + +- **Step 1** (identify jobs) → You get the RUN_ID needed for Step 2 +- **Step 2** (download) → You get the artifacts needed for Step 3 +- **Step 3** (extract) → You discover what failures exist for Step 4 +- **Step 4** (read messages) → You understand the errors to analyze in Step 5 +- **Step 5** (context) → You gather information to make recommendations in Step 6 +- **Step 6** (interpret) → You use all above to recommend fixes + +**Real Problem We Had**: +- ❌ Jumped to Step 3 without Step 1-2 +- ❌ Used random test data from context instead of downloading PR artifacts +- ❌ Skipped Steps 5-6 entirely +- ❌ Made recommendations without full context + +**Result**: Wrong analysis and recommendations that didn't actually fix the problem. + +### Recommended: Use the Automated Script + +```powershell +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR +``` + +This enforces the workflow and prevents skipping. + +### Step 2: Get Test Results + +Fetch the test result artifacts and job logs: + +```powershell +# Download artifacts including test XML results +gh run download --dir ./artifacts + +# Get job logs +gh run view --log > test-logs.txt + +# Inspect test XML +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$xml.'test-results' | Select-Object total, failures, errors, ignored, inconclusive +``` + +### Step 3: Extract Specific Failures + +Find the failing test cases in the XML: + +```powershell +# Get all failed test cases +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + +# For each failure, display key info +$failures | ForEach-Object { + [PSCustomObject]@{ + Name = $_.name + Description = $_.description + Message = $_.failure.message + StackTrace = $_.failure.'stack-trace' + } +} +``` + +### Step 4: Read the Error Message + +The error message tells you what went wrong: + +**Assertion failures:** +``` +Expected $true but got $false +Expected "value1" but got "value2" +Expression should have failed with exception, but didn't +``` + +**Exceptions:** +``` +Cannot find a parameter with name 'Name' +Property 'Property' does not exist on 'Object' +Cannot bind argument to parameter because it is null +``` + +**Timeouts:** +``` +Test timed out after 30 seconds +Test is taking too long to complete +``` + +### Step 5: Understand the Context + +Look at the test file to understand what was being tested: + +```powershell +# Find the test file mentioned in the stack trace +# Example: /path/to/test/Feature.Tests.ps1:42 + +# Read the test code around that line +code : + +# Understand: +# - What assertion is on that line? +# - What is the test trying to verify? +# - What are the setup/mock/before conditions? +# - Are there recent changes to the function being tested? +``` + +### Step 6: Interpret the Failure + +Determine the root cause category: + +**Test issue (needs code fix):** +- Assertion logic is wrong +- Test expectations don't match actual behavior +- Test setup is incomplete +- Mock/stub configuration missing + +**Environmental issue (needs environment change):** +- Test assumes a specific file or registry entry exists +- Test requires Windows/Linux specifically +- Test requires specific PowerShell version +- Test requires specific module version +- Timing-sensitive test affected by CI load + +**Data issue (needs input data change):** +- Test data no longer valid +- External API changed format +- Configuration file has changed structure + +**Flakiness (needs test hardening):** +- Race condition in test +- Timing assumptions too tight +- Resource contention on CI runner +- Non-deterministic behavior in tested code + +## Common Test Failure Patterns + +| Pattern | What It Means | Example | Next Step | +|---------|---------------|---------|-----------| +| `Expected $true but got $false` | Assertion on boolean result failed | Test expects function returns true, but it returns false | Check function logic for bug or test logic for wrong expectation | +| `Cannot find path` | File or directory doesn't exist | Test tries to read config file that's not present | Verify file path, check test setup, ensure CI environment has file | +| `Cannot bind argument to parameter 'X'` | Required parameter value is null or wrong type | Function called with $null where object expected | Check test mock setup, verify parameter types | +| `Test timed out after X seconds` | Test exceeded time limit | Network call or loop takes too long | Increase timeout for slow test, find infinite loop, mock network calls | +| `Expression should have failed but didn't` | Exception wasn't thrown when expected | Test expects error but function succeeds | Check if function behavior changed, update test expectation | +| `Could not find parameter 'X'` | Function doesn't have parameter | Test calls function with parameter that doesn't exist | Check PowerShell version, verify function signature, update test | +| `This platform is not supported` | Test skipped on current OS | Windows-only test running on Linux | Add platform check, update test environment, or mark as platform-specific | +| `Test marked [Ignore]` | Test explicitly disabled | Test has `[Ignore("reason")]` attribute | Check if reason still valid, remove if issue fixed | + +## Interpreting Test Results + +### Test Result Counts + +Pester test outcomes are categorized as: + +| Count | Meaning | Notes | +|-------|---------|-------| +| `total` | Total number of test cases executed | Should match: passed + failed + errors + skipped + ignored | +| `failures` | Test assertions that failed | `Expected X but got Y` type failures | +| `errors` | Tests that threw exceptions | Unhandled PowerShell exceptions during test | +| `skipped` | Tests explicitly skipped (marked with `-Skip`) | Test code recognizes condition and skips | +| `ignored` | Tests marked as ignored (marked with `-Ignore`) | Test disabled intentionally, usually notes reason | +| `inconclusive` | Tests with unclear result | Rare; usually means test framework issue | +| `passed` | Tests with passing assertions | `total - failures - errors - skipped - ignored` | + +### Stack Trace Interpretation + +A stack trace shows where the failure occurred: + +``` +at /home/runner/work/PowerShell/test/Feature.Tests.ps1:42 + +Means: +- File: /home/runner/work/PowerShell/test/Feature.Tests.ps1 +- Line: 42 +- Look at that line to see which assertion failed +``` + +### Understanding Skipped Tests + +When XML shows `result="Ignored"` or `result="Skipped"`: + +```xml + + Only runs on Windows + +``` + +The reason explains why test didn't run. Not a failure, but important for understanding test coverage. + +## Providing Test Failure Analysis + +### Investigation Questions + +After gathering test output, ask yourself: + +1. **Is the test code correct?** + - Does the test assertion match the expected behavior? + - Are test expectations still valid? + - Has the function being tested changed? + +2. **Is the test setup correct?** + - Are mocks/stubs configured properly? + - Does the test environment have required files/configuration? + - Are preconditions (database, files, services) met? + +3. **Is this a code bug or test issue?** + - Does the tested function have a logic error? + - Or does the test have incorrect expectations? + +4. **Is this environment-specific?** + - Only fails on Windows/Linux? + - Only fails on CI but passes locally? + - Timing-dependent or resource-dependent? + +5. **Is this a known/expected failure?** + - Is there already an issue tracking this failure? + - Is the test marked as flaky or expected to fail? + - Does the skip/ignore reason still apply? + +### Recommendation Framework + +Based on your analysis: + +| Finding | Recommendation | +|---------|-----------------| +| Test logic is wrong | "Test assertion on line X is incorrect. Test expects Y but function correctly returns Z. Update test expectation." | +| Tested code has bug | "Function at file.ps1#L42 has logic error. When X happens, returns Y instead of Z. Fix the condition." | +| Missing test setup | "Test setup incomplete. Mock for dependency Y is not configured. Add `Mock Get-Y -MockWith { ... }`" | +| Environment issue | "Test is Windows-specific but running on Linux. Either add platform check or skip on non-Windows." | +| Flaky test | "Test is timing-sensitive (sleep 1 second). Increase timeout or use better synchronization." | +| Test should be skipped | "Test is marked Ignored for good reason. Keep it disabled until issue #12345 is fixed." | + +### Tone and Structure + +Provide analysis as: + +1. **Summary** (1 sentence): What test is failing and general category +2. **Failure Details** (2-3 sentences): What the test output says +3. **Root Cause** (1-2 sentences): Why it's failing (test bug vs. code bug vs. environment) +4. **Recommendation** (actionable): What should be done to fix it +5. **Context** (optional): Link to related code, issues, or recent changes + +## Examples + +### Example 1: Assertion Failure Due to Code Bug + +**Test Output:** +``` +Expected 5 but got 3 at /path/to/Test.ps1:42 +``` + +**Investigation:** +1. Look at line 42: `$result | Should -Be 5` +2. Check the test: It expects function to return 5 items +3. Check the function: It returns `$items | Where-Object {$_.Status -eq "Active"}` but the filter is wrong +4. Root cause: Function has logic error, not test error + +**Recommendation:** +``` +Test failure is due to a code bug: + +The test Set-Configuration should return 5 items but returns 3. + +Looking at the tested function at [module.ps1#L42](module.ps1#L42): + $activeItems = $items | Where-Object {$_.Status -eq "Active"} + +The issue is the filter condition. It's currently filtering by "Active" status, +but should include "Pending" status as well. + +Fix: Change line 42 to: + $activeItems = $items | Where-Object {$_.Status -ne "Disabled"} + +Then re-run the test to verify it now returns 5 items as expected. +``` + +### Example 2: Test Setup Issue + +**Test Output:** +``` +Cannot find path '/expected/config.json' because it does not exist at /path/to/Test.ps1:15 +``` + +**Investigation:** +1. Line 15 tries to read a config file +2. The test setup doesn't create this file +3. Works locally but fails on CI because CI doesn't have the same file + +**Recommendation:** +``` +Test setup is incomplete: + +The test Initialize-Config fails because it expects /expected/config.json but the test doesn't create this file. + +The test needs to ensure the config file exists. Currently line 12-14 doesn't set up the file: + + # Before: + # (no setup of config file) + + # After: + @{ setting1 = "value1"; setting2 = "value2" } | ConvertTo-Json | + Out-File $testConfigPath + +Alternatively, the test function should accept a parameter for the config path and use a temporary file: + param([string]$ConfigPath = (New-TemporaryFile)) + +Re-run the test to verify the config file is properly available. +``` + +### Example 3: Platform-Specific Test Failure + +**Test Output:** +``` +Test 'should read Windows Registry' failed on Linux runner +Cannot find path 'HKEY_LOCAL_MACHINE:\...' +``` + +**Investigation:** +1. Test assumes Windows Registry exists (Windows-only) +2. Running on Linux runner doesn't have Registry +3. Test should skip on non-Windows platforms + +**Recommendation:** +``` +Test is platform-specific but running on wrong platform: + +The test "should read Windows Registry" assumes Windows Registry exists but is running on Linux. + +Add a platform check to skip this test on non-Windows systems: + + It "should read Windows Registry" -Skip:$(-not $IsWindows) { + # test code here + } + +Or group Windows-only tests in a separate Describe block with platform check: + + Describe "Windows Registry Tests" -Skip:$(-not $IsWindows) { + # all Windows-specific tests here + } + +This allows the test to be skipped on Linux/Mac while still running on Windows CI. +``` + +## References + +- [Pester Testing Framework](https://pester.dev/) — Official documentation, best practices for test writing +- [Test Files](../../../test/) — PowerShell test suite in repository +- [GitHub Actions Documentation](https://docs.github.com/en/actions) — Understanding workflow runs and logs +- [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/) — Language reference for understanding test code + +## Tips + +1. **Read the error message first:** The error message is usually the most direct clue to the problem +2. **Check test vs. code blame:** Is the test wrong or is the code wrong? Look at both sides +3. **Verify test isolation:** Does one test failure affect others? Check for shared state or test ordering dependencies +4. **Test locally first:** Try running the failing test locally to reproduce and understand it better +5. **Check for environmental assumptions:** Windows-specific paths, module versions, file locations may differ on CI +6. **Look for skip/ignore patterns:** If a test is consistently ignored, check if the reason is still valid +7. **Compare passing vs. failing:** If test passes locally but fails on CI, the difference is usually environment-related +8. **Check recent changes:** Did a recent PR change the tested code or test itself? +9. **Understand Pester output format:** Different Pester versions, different `-ErrorAction`, `-WarningAction` produce different test results +10. **Don't assume CI is wrong:** Failures on CI often reveal real issues that local testing missed (network, file permissions, parallelization, etc.) + +## Additional Links + +- [PowerShell Repository](https://github.com/PowerShell/PowerShell) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Pester Testing Framework](https://github.com/Pester/Pester) diff --git a/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md new file mode 100644 index 00000000000..707f45560a9 --- /dev/null +++ b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md @@ -0,0 +1,163 @@ +# Understanding Pester Test Failures + +This reference explains how to interpret Pester test output and understand failure messages. + +## Supported Formats + +### Pester 4 Format +``` +at line: 123 in C:\path\to\file.ps1 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result +} +``` + +### Pester 5 Format (Common) +``` +at 1 | Should -Be 2, C:\path\to\file.ps1:123 +at 1 | Should -Be 2, /home/runner/work/PowerShell/PowerShell/test/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +### Alternative Format +``` +at C:\path\to\file.ps1:123 +at /path/to/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +## Troubleshooting Parsing Failures + +### Issue: Line Number Extracted But File Path Is Null + +**Cause:** Stack trace matches line-with-path pattern but file extraction doesn't work + +**Solution:** +1. Check if file path exists as expected in filesystem +2. Verify regex doesn't have too-greedy bounds (check use of `.+?` vs `.+`) +3. Test regex against actual stack trace string: + ```powershell + $trace = "at line: 42 in C:\path\to\test.ps1" + if ($trace -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + Write-Host "File: $($matches[2])" # Should be "C:\path\to\test.ps1" + } + ``` + +### Issue: Special Characters in File Path Break Regex + +**Cause:** Characters like parens `()`, brackets `[]`, pipes `|` have special meaning in regex + +**Solution:** +1. Escape special chars in regex: `[Regex]::Escape($path)` +2. Use character class `[\/\\]` instead of alternation for path separators +3. Test with files containing problematic names: + ```powershell + $traces = @( + "at line: 1 in C:\path\(with)\parens\test.ps1", + "at /home/user/[brackets]/test.ps1:5", + "at C:\path\with spaces\test.ps1:10" + ) + # Test each against all patterns + ``` + +### Issue: Regex Matches But Extracts Wrong Values + +**Symptom:** $matches[1] is file instead of line, or vice versa + +**Debug Steps:** +1. Print all captured groups: `$matches.Values | Format-Table -AutoSize` +2. Verify group order in regex matches expectations +3. Test with sample Pester output: + ```powershell + $sampleTrace = @" + at 1 | Should -Be 2, /home/runner/work/PowerShell/test/file.ps1:42 + "@ + + if ($sampleTrace -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + Write-Host "Match 1: $($matches[1])" # Should be file path + Write-Host "Match 2: $($matches[2])" # Should be line number + } + ``` + +## Testing the Parser + +Use this PowerShell script to validate `Get-PesterFailureFileInfo`: + +```powershell +# Import the function +. ./build.psm1 + +$testCases = @( + @{ + Input = "at line: 42 in C:\path\to\test.ps1" + Expected = @{ File = "C:\path\to\test.ps1"; Line = "42" } + }, + @{ + Input = "at /home/runner/work/test.ps1:123" + Expected = @{ File = "/home/runner/work/test.ps1"; Line = "123" } + }, + @{ + Input = "at 1 | Should -Be 2, /path/to/file.ps1:99" + Expected = @{ File = "/path/to/file.ps1"; Line = "99" } + } +) + +foreach ($test in $testCases) { + $result = Get-PesterFailureFileInfo -StackTraceString $test.Input + + $fileMatch = $result.File -eq $test.Expected.File + $lineMatch = $result.Line -eq $test.Expected.Line + $status = if ($fileMatch -and $lineMatch) { "✓ PASS" } else { "✗ FAIL" } + + Write-Host "$status : $($test.Input)" + if (-not $fileMatch) { Write-Host " Expected file: $($test.Expected.File), got: $($result.File)" } + if (-not $lineMatch) { Write-Host " Expected line: $($test.Expected.Line), got: $($result.Line)" } +} +``` + +## Adding Support for New Formats + +When Pester changes its output format: + +1. **Capture sample output** from failing tests +2. **Identify the pattern** (e.g., "file path always after comma followed by colon") +3. **Write regex** to match pattern without over-matching +4. **Add to `Get-PesterFailureFileInfo`** before existing patterns (order matters for fallback) +5. **Test with samples** containing special characters, long paths, and edge cases + +Example: Adding a new format at the top of the function: + +```powershell +# Try pattern: "at , :" (Pester 5.1 hypothetical) +if ($StackTraceString -match 'at .+?, ((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} + +# Try existing patterns... +``` + +Place new patterns **first** so they take precedence over fallback patterns. diff --git a/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 new file mode 100644 index 00000000000..12486596071 --- /dev/null +++ b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 @@ -0,0 +1,456 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Automated Pester test failure analysis workflow for GitHub PRs. + +.DESCRIPTION + This script automates the complete analysis workflow defined in the analyze-pester-failures + skill. It performs all steps in order: + 1. Identify failing test jobs in the PR + 2. Download test artifacts and logs + 3. Extract specific test failures + 4. Parse error messages + 5. Search logs for error markers and generate recommendations + + By automating the workflow, this ensures analysis steps are followed in order + and nothing is skipped. + +.PARAMETER PR + The GitHub PR number to analyze (e.g., 26800) + +.PARAMETER Owner + Repository owner (default: PowerShell) + +.PARAMETER Repo + Repository name (default: PowerShell) + +.PARAMETER OutputDir + Directory to store analysis results (default: ./pester-analysis-PR) + +.PARAMETER Interactive + Prompt for recommendations after analysis (default: non-interactive) + +.PARAMETER ForceDownload + Force re-download of artifacts and logs, even if they already exist + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 + Analyzes PR #26800 and saves results to ./pester-analysis-PR26800 + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -Interactive + Interactive mode: shows failures and prompts for next steps + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -ForceDownload + Re-download all logs and artifacts, skipping the cache + +.NOTES + Requires: GitHub CLI (gh) configured and authenticated + This script enforces the workflow defined in .github/skills/analyze-pester-failures/SKILL.md +#> + +param( + [Parameter(Mandatory)] + [int]$PR, + + [string]$Owner = 'PowerShell', + [string]$Repo = 'PowerShell', + [string]$OutputDir, + [switch]$Interactive, + [switch]$ForceDownload +) + +$ErrorActionPreference = 'Stop' + +if (-not $OutputDir) { + $OutputDir = "./pester-analysis-PR$PR" +} + +# Colors for output +$colors = @{ + Step = [ConsoleColor]::Cyan + Success = [ConsoleColor]::Green + Warning = [ConsoleColor]::Yellow + Error = [ConsoleColor]::Red + Info = [ConsoleColor]::Gray +} + +function Write-Step { + param([string]$text, [int]$number) + Write-Host "`n[$number/6] $text" -ForegroundColor $colors.Step -BackgroundColor Black +} + +function Write-Result { + param([string]$text, [ValidateSet('Success','Warning','Error','Info')]$type = 'Info') + Write-Host $text -ForegroundColor $colors[$type] +} + +Write-Host "`n=== Pester Test Failure Analysis ===" -ForegroundColor $colors.Step +Write-Host "PR: $Owner/$Repo#$PR" -ForegroundColor $colors.Info +Write-Host "Output Directory: $OutputDir" -ForegroundColor $colors.Info + +# Ensure output directory exists +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null +} + +# STEP 1: Identify the Failing Test Job +Write-Step "Identify failing test jobs" 1 + +Write-Result "Fetching PR status checks..." Info +$prResponse = gh pr view $PR --repo "$Owner/$Repo" --json 'statusCheckRollup' | ConvertFrom-Json +$allChecks = $prResponse.statusCheckRollup + +$failedJobs = $allChecks | Where-Object { $_.conclusion -eq 'FAILURE' } + +if (-not $failedJobs) { + Write-Result "✓ No failed jobs found" Success + Write-Host " Total checks: $($allChecks.Count)" + $allChecks | Where-Object { $_ } | ForEach-Object { + Write-Host " - $($_.name): $($_.conclusion)" -ForegroundColor $colors.Info + } + exit 0 +} + +Write-Result "✓ Found $($failedJobs.Count) failing job(s)" Warning + +$failedJobs | Where-Object { $_.conclusion -eq 'FAILURE' } | ForEach-Object { + Write-Host " ✗ $($_.name) - $($_.conclusion)" -ForegroundColor $colors.Error + if ($_.detailsUrl) { + Write-Host " URL: $($_.detailsUrl)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 2..." + Read-Host | Out-Null +} + +# STEP 2: Get Test Results +Write-Step "Download test artifacts and logs" 2 + +# Extract unique run IDs from failing jobs +$uniqueRuns = @() +foreach ($failedJob in $failedJobs) { + if ($failedJob.detailsUrl -match 'runs/(\d+)') { + $runId = $matches[1] + if ($runId -notin $uniqueRuns) { + $uniqueRuns += $runId + } + } +} + +if ($uniqueRuns.Count -eq 0) { + Write-Result "✗ Could not extract run IDs from failing jobs" Error + exit 1 +} + +Write-Result "Found $($uniqueRuns.Count) run(s): $($uniqueRuns -join ', ')" Info + +$artifactDir = Join-Path $OutputDir artifacts + +# Check if artifacts already exist +$existingArtifacts = Get-ChildItem $artifactDir -Recurse -File -ErrorAction SilentlyContinue + +if ($existingArtifacts -and -not $ForceDownload) { + Write-Result "✓ Artifacts already downloaded" Success + $existingArtifacts | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } +} else { + Write-Result "Downloading artifacts from run $($uniqueRuns[0])..." Info + gh run download $uniqueRuns[0] --dir $artifactDir --repo "$Owner/$Repo" 2>&1 | Out-Null + + if (Test-Path $artifactDir) { + Write-Result "✓ Artifacts downloaded" Success + Get-ChildItem $artifactDir -Recurse -File | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } + } else { + Write-Result "✗ Failed to download artifacts" Error + exit 1 + } +} + +# Download individual job logs for failing jobs +Write-Result "Downloading individual job logs..." Info + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + New-Item -ItemType Directory -Path $logsDir -Force | Out-Null +} + +# Check if logs already exist +$existingLogs = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + +if ($existingLogs -and -not $ForceDownload) { + Write-Result "✓ Job logs already downloaded" Success + $existingLogs | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} else { + # Process each run and get its jobs + $failedJobIds = @() + foreach ($runId in $uniqueRuns) { + $runJobs = gh run view $runId --repo "$Owner/$Repo" --json jobs | ConvertFrom-Json + + foreach ($failedJob in $failedJobs) { + # Check if this failed job belongs to this run + if ($failedJob.detailsUrl -match "runs/$runId/") { + $jobMatch = $runJobs.jobs | Where-Object { $_.name -eq $failedJob.name } | Select-Object -First 1 + if ($jobMatch) { + $failedJobIds += @{ + name = $failedJob.name + id = $jobMatch.databaseId + runId = $runId + } + } + } + } + } + + # Download logs for all failed jobs + foreach ($jobInfo in $failedJobIds) { + $logFile = Join-Path $logsDir ("log-{0}.txt" -f ($jobInfo.name -replace '[^a-zA-Z0-9-]', '_')) + Write-Result " Downloading: $($jobInfo.name) (Run $($jobInfo.runId))" Info + gh run view $jobInfo.runId --log --job $jobInfo.id --repo "$Owner/$Repo" > $logFile 2>&1 + } + + Write-Result "✓ Job logs downloaded" Success + Get-ChildItem $logsDir -Filter "*.txt" | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 3..." + Read-Host | Out-Null +} + +# STEP 3: Extract Specific Failures +Write-Step "Extract test failures from XML" 3 + +$xmlFiles = Get-ChildItem $artifactDir -Filter "*.xml" -Recurse +if (-not $xmlFiles) { + Write-Result "✗ No test result XML files found" Error + exit 1 +} + +Write-Result "✓ Found $($xmlFiles.Count) test result file(s)" Success + +$allFailures = @() + +foreach ($xmlFile in $xmlFiles) { + Write-Result "`nParsing: $($xmlFile.Name)" Info + + try { + [xml]$xml = Get-Content $xmlFile + $testResults = $xml.'test-results' + + Write-Host " Total: $($testResults.total)" -ForegroundColor $colors.Info + Write-Host " Passed: $($testResults.passed)" -ForegroundColor $colors.Success + if ($testResults.failures -gt 0) { + Write-Host " Failed: $($testResults.failures)" -ForegroundColor $colors.Error + } + if ($testResults.errors -gt 0) { + Write-Host " Errors: $($testResults.errors)" -ForegroundColor $colors.Error + } + if ($testResults.skipped -gt 0) { + Write-Host " Skipped: $($testResults.skipped)" -ForegroundColor $colors.Warning + } + if ($testResults.ignored -gt 0) { + Write-Host " Ignored: $($testResults.ignored)" -ForegroundColor $colors.Warning + } + + # Extract failures + $failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + + foreach ($failure in $failures) { + $allFailures += @{ + Name = $failure.name + File = $xmlFile.Name + Message = $failure.failure.message + StackTrace = $failure.failure.'stack-trace' + } + } + } catch { + Write-Result "✗ Error parsing XML: $_" Error + } +} + +Write-Result "`n✓ Extracted $($allFailures.Count) failures total" Success + +# Save failures to JSON for later analysis +$allFailures | ConvertTo-Json -Depth 10 | Out-File (Join-Path $OutputDir "failures.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 4..." + Read-Host | Out-Null +} + +# STEP 4: Read Error Messages +Write-Step "Analyze error messages" 4 + +$failuresByType = @{} + +foreach ($failure in $allFailures) { + $message = $failure.Message -split "`n" | Select-Object -First 1 + + # Categorize failure + $type = 'Other' + if ($message -match 'Expected .* but got') { $type = 'Assertion' } + elseif ($message -match 'Cannot (find|bind)') { $type = 'Exception' } + elseif ($message -match 'timed out') { $type = 'Timeout' } + + if (-not $failuresByType[$type]) { + $failuresByType[$type] = @() + } + $failuresByType[$type] += $failure +} + +Write-Result "Failure breakdown:" Info +$failuresByType.GetEnumerator() | ForEach-Object { + Write-Host " $($_.Key): $($_.Value.Count)" -ForegroundColor $colors.Warning +} + +Write-Result "`nTop failure messages:" Info +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object { + Write-Host " [$($_.Count)x] $($_.Name -split "`n" | Select-Object -First 1)" -ForegroundColor $colors.Info +} + +# Save analysis +$analysis = @{ + FailuresByType = @{} + TopMessages = @() +} + +$failuresByType.GetEnumerator() | ForEach-Object { + $analysis.FailuresByType[$_.Key] = $_.Value.Count +} + +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { + $analysis.TopMessages += @{ + Count = $_.Count + Message = ($_.Name -split "`n" | Select-Object -First 1) + } +} + +$analysis | ConvertTo-Json | Out-File (Join-Path $OutputDir "analysis.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 5..." + Read-Host | Out-Null +} + +# STEP 5: Search Logs for Error Markers +Write-Step "Search logs for error markers" 5 + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + Write-Result "⚠ Logs directory not found" Warning +} else { + $logFiles = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + if (-not $logFiles) { + Write-Result "⚠ No log files found in logs directory" Warning + } else { + Write-Result "Searching $($logFiles.Count) job log(s) for error markers ([-])" Info + Write-Result "Format: [JobName] [LineNumber] Content" Info + Write-Host "" + + $allErrorLines = @() + + foreach ($logFile in $logFiles) { + $jobName = $logFile.BaseName -replace '^log-', '' + $logLines = @(Get-Content $logFile) + + for ($i = 0; $i -lt $logLines.Count; $i++) { + $line = $logLines[$i] + if ($line -match '\s\[-\]\s') { + $allErrorLines += @{ + JobName = $jobName + LineNumber = $i + 1 + Content = $line + } + } + } + } + + if ($allErrorLines.Count -gt 0) { + Write-Result "✓ Found $($allErrorLines.Count) error marker line(s)" Warning + + $allErrorLines | ForEach-Object { + Write-Host " [$($_.JobName)] [$($_.LineNumber)] $($_.Content)" -ForegroundColor $colors.Error + } + + # Save to file + $allErrorLines | ConvertTo-Json | Out-File (Join-Path $OutputDir "error-markers.json") + Write-Result "✓ Error markers saved to error-markers.json" Success + } else { + Write-Result "✓ No error markers found in logs" Success + } + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 6..." + Read-Host | Out-Null +} + +# STEP 6: Generate Recommendations +Write-Step "Generate recommendations" 6 + +$recommendations = @() + +# Analyze patterns +if ($failuresByType['Assertion']) { + $recommendations += "Multiple assertion failures detected. These indicate test expectations don't match actual behavior." +} + +if ($failuresByType['Exception']) { + $recommendations += "Exception errors found. Check test setup and prerequisites - may indicate missing files, modules, or permissions." +} + +if ($failuresByType['Timeout']) { + $recommendations += "Timeout failures suggest slow or hanging operations. Consider network issues or resource constraints on CI." +} + +# Check for patterns in failure messages +$failureMessages = $allFailures.Message -join "`n" +if ($failureMessages -match 'PackageManagement') { + $recommendations += "PackageManagement module issues detected. Verify module availability and help repository access." +} + +if ($failureMessages -match 'Update-Help') { + $recommendations += "Update-Help failures detected. Check network connectivity to help repository and help installation paths." +} + +Write-Result "`n📋 Recommendations:" Info +if ($recommendations) { + $recommendations | ForEach-Object { Write-Host " • $_" -ForegroundColor $colors.Info } +} else { + Write-Host " • Review failures in detail" -ForegroundColor $colors.Info + Write-Host " • Check if test changes are needed" -ForegroundColor $colors.Info + Write-Host " • Consider environment-specific issues" -ForegroundColor $colors.Info +} + +$recommendations | Out-File (Join-Path $OutputDir "recommendations.txt") + +# Summary +Write-Host "`n=== Analysis Complete ===" -ForegroundColor $colors.Step +Write-Host "Results saved to: $OutputDir" -ForegroundColor $colors.Info +Write-Host " - failures.json (detailed failure data)" -ForegroundColor $colors.Info +Write-Host " - analysis.json (summary analysis)" -ForegroundColor $colors.Info +Write-Host " - recommendations.txt (suggested fixes)" -ForegroundColor $colors.Info +Write-Host " - error-markers.json (error markers from logs)" -ForegroundColor $colors.Info +Write-Host " - logs/ (individual job log files)" -ForegroundColor $colors.Info +Write-Host " - artifacts/ (downloaded test artifacts)" -ForegroundColor $colors.Info + +Write-Host "`nNext steps:" -ForegroundColor $colors.Step +Write-Host "1. Review recommendations.txt for analysis" -ForegroundColor $colors.Info +Write-Host "2. Examine failures.json for detailed error messages" -ForegroundColor $colors.Info +Write-Host "3. Check error-markers.json for specific test failures in logs" -ForegroundColor $colors.Info +Write-Host "4. Review individual job logs in logs/ directory for contextual details" -ForegroundColor $colors.Info +Write-Host "`n" diff --git a/.github/workflows/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/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml new file mode 100644 index 00000000000..3ef564eba90 --- /dev/null +++ b/.github/workflows/analyze-reusable.yml @@ -0,0 +1,77 @@ +name: CodeQL Analysis (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for CodeQL analysis' + type: string + required: false + default: ubuntu-latest + +permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +jobs: + analyze: + name: Analyze + runs-on: ${{ inputs.runner_os }} + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: '0' + + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - run: | + Import-Module .\tools\ci.psm1 + Show-Environment + name: Capture Environment + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + name: Bootstrap + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild -Configuration 'StaticAnalysis' + name: Build + shell: pwsh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000..d78e745a4a9 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,64 @@ +name: "Copilot Setup Steps" + +# Allow testing of the setup steps from your repository's "Actions" tab. +on: + workflow_dispatch: + + pull_request: + branches: + - master + paths: + - ".github/workflows/copilot-setup-steps.yml" + +permissions: + contents: read + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + # See https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Bootstrap + if: success() + run: |- + $title = 'Import Build.psm1' + Write-Host "::group::$title" + Import-Module ./build.psm1 -Verbose -ErrorAction Stop + Write-LogGroupEnd -Title $title + + $title = 'Switch to public feed' + Write-LogGroupStart -Title $title + Switch-PSNugetConfig -Source Public + Write-LogGroupEnd -Title $title + + $title = 'Bootstrap' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario DotNet + Write-LogGroupEnd -Title $title + + $title = 'Install .NET Tools' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario Tools + Write-LogGroupEnd -Title $title + + $title = 'Sync Tags' + Write-LogGroupStart -Title $title + Sync-PSTags -AddRemoteIfMissing + Write-LogGroupEnd -Title $title + + $title = 'Setup .NET environment variables' + Write-LogGroupStart -Title $title + Find-DotNet -SetDotnetRoot + Write-LogGroupEnd -Title $title + shell: pwsh diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml deleted file mode 100644 index 3447bf1803d..00000000000 --- a/.github/workflows/daily.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -name: PowerShell Daily -on: - 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 "name=OLD_VERSION::$currentVersion" -Verbose - Write-Host "::set-env name=OLD_VERSION::$currentVersion" - - ./tools/UpdateDotnetRuntime.ps1 -UpdateMSIPackaging - $newVersion = (Get-Content .\global.json | ConvertFrom-Json).sdk.version - Write-Verbose "name=NEW_VERSION::$newVersion" -Verbose - Write-Host "::set-env name=NEW_VERSION::$newVersion" - - if ($currentVersion -ne $newVersion) { - Write-Verbose "name=CREATE_PR::true" -Verbose - Write-Host "::set-env name=CREATE_PR::true" - } - - 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..84f0b03fa32 --- /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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: 'Dependency Review' + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 00000000000..27ceac59bbd --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Verify PR Labels + +on: + pull_request: + types: [opened, reopened, edited, labeled, unlabeled, synchronize] + +permissions: + contents: read + pull-requests: read + +jobs: + verify-labels: + if: github.repository_owner == 'PowerShell' + runs-on: ubuntu-latest + + steps: + - name: Check out the repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Verify PR has label starting with 'cl-' + id: verify-labels + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const labels = context.payload.pull_request.labels.map(label => label.name.toLowerCase()); + if (!labels.some(label => label.startsWith('cl-'))) { + core.setFailed("Every PR must have at least one label starting with 'cl-'."); + } diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 00000000000..77186125a9c --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,262 @@ +name: Linux-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + workflow_dispatch: + + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + FORCE_FEATURE: 'False' + FORCE_PACKAGE: 'False' + NUGET_KEY: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + system_debug: 'false' +jobs: + changes: + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + name: Change Detection + runs-on: ubuntu-latest + # Required permissions + permissions: + pull-requests: read + contents: read + + # Set job outputs to values from filter step + outputs: + source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && (startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell') + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" + + ci_build: + name: Build PowerShell + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Build + uses: "./.github/actions/build/ci" + linux_test_unelevated_ci: + name: Linux Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Unelevated CI + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + linux_test_elevated_ci: + name: Linux Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Elevated CI + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + linux_test_unelevated_others: + name: Linux Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Unelevated Others + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + linux_test_elevated_others: + name: Linux Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: ubuntu-latest + test_results_artifact_name: testResults-xunit + + infrastructure_tests: + name: Infrastructure Tests + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + + - name: Install Pester + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Install-CIPester + + - name: Run Infrastructure Tests + shell: pwsh + run: | + $testResultsFolder = Join-Path $PWD "testResults" + New-Item -ItemType Directory -Path $testResultsFolder -Force | Out-Null + + $config = New-PesterConfiguration + $config.Run.Path = './test/infrastructure/' + $config.Run.PassThru = $true + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'NUnitXml' + $config.TestResult.OutputPath = "$testResultsFolder/InfrastructureTests.xml" + $config.Output.Verbosity = 'Detailed' + + $result = Invoke-Pester -Configuration $config + + if ($result.FailedCount -gt 0 -or $result.Result -eq 'Failed') { + throw "Infrastructure tests failed" + } + + - name: Publish Test Results + uses: "./.github/actions/test/process-pester-results" + if: always() + with: + name: "InfrastructureTests" + testResultsFolder: "${{ github.workspace }}/testResults" + + ## Temporarily disable the CodeQL analysis on Linux as it doesn't work for .NET SDK 10-rc.2. + # analyze: + # name: CodeQL Analysis + # needs: changes + # if: ${{ needs.changes.outputs.source == 'true' }} + # uses: ./.github/workflows/analyze-reusable.yml + # permissions: + # actions: read + # contents: read + # security-events: write + # with: + # runner_os: ubuntu-latest + + ready_to_merge: + name: Linux ready to merge + needs: + - xunit_tests + - linux_test_elevated_ci + - linux_test_elevated_others + - linux_test_unelevated_ci + - linux_test_unelevated_others + - linux_packaging + - merge_conflict_check + - infrastructure_tests + # - analyze + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 + with: + needs_context: ${{ toJson(needs) }} + linux_packaging: + name: Linux Packaging + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - name: Linux Packaging + uses: "./.github/actions/test/linux-packaging" diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml new file mode 100644 index 00000000000..55d852bb68a --- /dev/null +++ b/.github/workflows/macos-ci.yml @@ -0,0 +1,248 @@ +name: macOS-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + FORCE_FEATURE: 'False' + FORCE_PACKAGE: 'False' + HOMEBREW_NO_ANALYTICS: 1 + NUGET_KEY: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + system_debug: 'false' + +jobs: + changes: + name: Change Detection + runs-on: ubuntu-latest + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + # Required permissions + permissions: + pull-requests: read + contents: read + + # Set job outputs to values from filter step + outputs: + source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci_build: + name: Build PowerShell + runs-on: macos-15-large + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Build + uses: "./.github/actions/build/ci" + macos_test_unelevated_ci: + name: macos Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Unelevated CI + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + macos_test_elevated_ci: + name: macOS Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Elevated CI + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + macos_test_unelevated_others: + name: macOS Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Unelevated Others + uses: "./.github/actions/test/nix" + with: + purpose: UnelevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + macos_test_elevated_others: + name: macOS Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: macos-15-large + test_results_artifact_name: testResults-xunit + PackageMac-macos_packaging: + name: macOS packaging and testing + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + runs-on: + - macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + - name: Bootstrap packaging + if: success() + run: |- + import-module ./build.psm1 + start-psbootstrap -Scenario package + shell: pwsh + - name: Build PowerShell and Create macOS package + if: success() + run: |- + import-module ./build.psm1 + import-module ./tools/ci.psm1 + import-module ./tools/packaging/packaging.psm1 + Switch-PSNugetConfig -Source Public + Sync-PSTags -AddRemoteIfMissing + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration Release -PSModuleRestore -ReleaseTag $releaseTag + $macOSRuntime = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'osx-arm64' } else { 'osx-x64' } + Start-PSPackage -Type osxpkg -ReleaseTag $releaseTag -MacOSRuntime $macOSRuntime -SkipReleaseChecks + shell: pwsh + + - name: Install Pester + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + + - name: Test package contents + if: success() + run: |- + $env:PACKAGE_FOLDER = Get-Location + $testResultsPath = Join-Path $env:RUNNER_WORKSPACE "testResults" + if (-not (Test-Path $testResultsPath)) { + New-Item -ItemType Directory -Path $testResultsPath -Force | Out-Null + } + Import-Module Pester + $pesterConfig = New-PesterConfiguration + $pesterConfig.Run.Path = './test/packaging/macos/package-validation.tests.ps1' + $pesterConfig.Run.PassThru = $true + $pesterConfig.Output.Verbosity = 'Detailed' + $pesterConfig.TestResult.Enabled = $true + $pesterConfig.TestResult.OutputFormat = 'NUnitXml' + $pesterConfig.TestResult.OutputPath = Join-Path $testResultsPath "macOSPackage.xml" + $result = Invoke-Pester -Configuration $pesterConfig + if ($result.FailedCount -gt 0) { + throw "Package validation failed with $($result.FailedCount) failed test(s)" + } + shell: pwsh + - name: Publish and Upload Pester Test Results + if: always() + uses: "./.github/actions/test/process-pester-results" + with: + name: "macOSPackage" + testResultsFolder: "${{ runner.workspace }}/testResults" + - name: Upload package artifact + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: macos-package + path: "*.pkg" + ready_to_merge: + name: macos ready to merge + needs: + - xunit_tests + - PackageMac-macos_packaging + - macos_test_elevated_ci + - macos_test_elevated_others + - macos_test_unelevated_ci + - macos_test_unelevated_others + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 + with: + needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/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/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 00000000000..935c8ff93bb --- /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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + 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@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 + with: + sarif_file: results.sarif diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml new file mode 100644 index 00000000000..19da648a959 --- /dev/null +++ b/.github/workflows/verify-markdown-links.yml @@ -0,0 +1,32 @@ +name: Verify Markdown Links + +on: + push: + branches: [ main, master ] + paths: + - '**/*.md' + - '.github/workflows/verify-markdown-links.yml' + - '.github/actions/infrastructure/markdownlinks/**' + pull_request: + branches: [ main, master ] + paths: + - '**/*.md' + schedule: + # Run weekly on Sundays at midnight UTC to catch external link rot + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + verify-markdown-links: + name: Verify Markdown Links + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Verify markdown links + id: verify + uses: ./.github/actions/infrastructure/markdownlinks + with: + timeout-sec: 30 + maximum-retry-count: 2 diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml new file mode 100644 index 00000000000..8a57b8b9726 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,194 @@ +name: Windows-CI +on: + workflow_dispatch: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.vsts-ci/misc-analysis.yml" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!test/perf/**" + - "!.pipelines/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" + +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +permissions: + contents: read + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" + NugetSecurityAnalysisWarningLevel: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts +jobs: + changes: + name: Change Detection + runs-on: ubuntu-latest + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + # Required permissions + permissions: + pull-requests: read + contents: read + + # Set job outputs to values from filter step + outputs: + source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci_build: + name: Build PowerShell + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Build + uses: "./.github/actions/build/ci" + windows_test_unelevated_ci: + name: Windows Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Unelevated CI + uses: "./.github/actions/test/windows" + with: + purpose: UnelevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + windows_test_elevated_ci: + name: Windows Elevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Elevated CI + uses: "./.github/actions/test/windows" + with: + purpose: ElevatedPesterTests + tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + windows_test_unelevated_others: + name: Windows Unelevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Unelevated Others + uses: "./.github/actions/test/windows" + with: + purpose: UnelevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + windows_test_elevated_others: + name: Windows Elevated Others + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Elevated Others + uses: "./.github/actions/test/windows" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: windows-latest + test_results_artifact_name: testResults-xunit + analyze: + name: CodeQL Analysis + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/analyze-reusable.yml + permissions: + actions: read + contents: read + security-events: write + with: + runner_os: windows-latest + windows_packaging: + name: Windows Packaging + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + uses: ./.github/workflows/windows-packaging-reusable.yml + ready_to_merge: + name: windows ready to merge + needs: + - xunit_tests + - windows_test_elevated_ci + - windows_test_elevated_others + - windows_test_unelevated_ci + - windows_test_unelevated_others + - analyze + - windows_packaging + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 + with: + needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml new file mode 100644 index 00000000000..8d0255d4443 --- /dev/null +++ b/.github/workflows/windows-packaging-reusable.yml @@ -0,0 +1,92 @@ +name: Windows Packaging (Reusable) + +on: + workflow_call: + +env: + GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts + +permissions: + contents: read + +jobs: + package: + name: ${{ matrix.architecture }} - ${{ matrix.channel }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - architecture: x64 + channel: preview + runtimePrefix: win7 + - architecture: x86 + channel: stable + runtimePrefix: win7 + - architecture: x86 + channel: preview + runtimePrefix: win7 + - architecture: arm64 + channel: preview + runtimePrefix: win + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Capture Environment + if: success() || failure() + run: | + Import-Module .\tools\ci.psm1 + Show-Environment + shell: pwsh + + - name: Capture PowerShell Version Table + if: success() || failure() + run: | + $PSVersionTable + shell: pwsh + + - name: Switch to Public Feeds + if: success() + run: | + Import-Module .\tools\ci.psm1 + Switch-PSNugetConfig -Source Public + shell: pwsh + + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + - name: Bootstrap + if: success() + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + shell: pwsh + + - name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + New-CodeCoverageAndTestPackage + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh + + - name: Upload Build Artifacts + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} + path: | + ${{ github.workspace }}/artifacts/**/* + !${{ github.workspace }}/artifacts/**/*.pdb diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml new file mode 100644 index 00000000000..c643917edd0 --- /dev/null +++ b/.github/workflows/xunit-tests.yml @@ -0,0 +1,56 @@ +name: xUnit Tests (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for xUnit tests' + type: string + required: false + default: ubuntu-latest + test_results_artifact_name: + description: 'Artifact name for xUnit test results directory' + type: string + required: false + default: testResults-xunit + +permissions: + contents: read + +jobs: + xunit: + name: Run xUnit Tests + runs-on: ${{ inputs.runner_os }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing + + - name: Build PowerShell and run xUnit tests + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild + Write-Host "Running full xUnit test suite (no skipping)..." + Invoke-CIxUnit + Write-Host "Completed xUnit test run." + + - name: Upload xUnit results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: ${{ inputs.test_results_artifact_name }} + path: ${{ github.workspace }}/xUnitTestResults.xml diff --git a/.gitignore b/.gitignore index fb19bcffa77..48556cf1b8c 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 @@ -82,6 +83,9 @@ TestsResults*.xml ParallelXUnitResults.xml xUnitResults.xml +# Attack Surface Analyzer results +asa-results/ + # Resharper settings PowerShell.sln.DotSettings.user *.msp @@ -89,3 +93,34 @@ 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 + +# Pester test failure analysis results (generated by analyze-pr-test-failures.ps1) +**/pester-analysis-*/ + +# Ignore CTRF report files +crtf/* diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 00000000000..e0dd4ccb9e5 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,2293 @@ +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 + +# 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 + +# 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 + +# 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 +# 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 = 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 +# # 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 = warning + +# 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 +# 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 +# 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 + +# 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 + +# CA1852: Seal internal types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852 +dotnet_diagnostic.CA1852.severity = warning + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853 +dotnet_diagnostic.CA1853.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858 +dotnet_diagnostic.CA1858.severity = warning + +# CA1860: Avoid using 'Enumerable.Any()' extension method +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860 +dotnet_diagnostic.CA1860.severity = warning + +# CA1865: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865 +dotnet_diagnostic.CA1865.severity = warning + +# CA1866: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1866 +dotnet_diagnostic.CA1866.severity = warning + +# CA1867: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1867 +dotnet_diagnostic.CA1867.severity = warning + +# CA1868: Unnecessary call to 'Contains' for sets +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 +dotnet_diagnostic.CA1868.severity = warning + +# 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 + +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021 +dotnet_diagnostic.CA2021.severity = warning + +# CA2022: Avoid inexact read with 'Stream.Read' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022 +dotnet_diagnostic.CA2022.severity = warning + +# CA2100: Review SQL queries for security vulnerabilities +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100 +dotnet_diagnostic.CA2100.severity = none + +# 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 +# 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 +# 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 = warning + +# 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 +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0029 +dotnet_diagnostic.IDE0029.severity = warning + +# IDE0030: UseCoalesceExpressionForNullable +# 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 +# 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 +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0049 +dotnet_diagnostic.IDE0049.severity = suggestion + +# 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 + +# IDE0070: UseSystemHashCode +# 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 = warning + +# 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 +# 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 +# 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 = warning + +# 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..ce974fe69e5 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/serviceModel.json", + "contentVersion": "1.0.0.0", + "serviceMetadata": { + "serviceGroup": "OneBranch-PowerShellDocker", + "environment": "Test" + }, + "serviceResourceGroupDefinitions": [ + { + "name": "OneBranch-PowerShellDocker-RGDef", + "serviceResourceDefinitions": [ + { + "name": "OneBranch-PowerShellDocker.Shell-SRDef", + "composedOf": { + "extension": { + "shell": [ + { + "type": "Run", + "properties": { + "imageName": "adm-azurelinux-30-l", + "imageVersion": "v2" + } + } + ] + } + } + } + ] + } + ], + "serviceResourceGroups": [ + { + "azureResourceGroupName": "default", + "location": "West US 3", + "instanceOf": "OneBranch-PowerShellDocker-RGDef", + "azureSubscriptionId": "default", + "scopeTags": [ + { + "name": "Global" + } + ], + "serviceResources": [ + { + "Name": "LinuxContainerUpload", + "InstanceOf": "OneBranch-PowerShellDocker.Shell-SRDef", + "RolloutParametersPath": "UploadLinux.Rollout.json" + } + ] + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 new file mode 100644 index 00000000000..6797ff94575 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -0,0 +1,397 @@ +<# +This function gets info from pmc's derived list of all repositories and from mapping.json (which contains info on just the repositories powershell publishes packages to, their package formats, etc) +to create a list of repositories PowerShell cares about along with repository Ids, repository full Urls and associated package that will be published to it. +#> +function Get-MappedRepositoryIds { + param( + [Parameter(Mandatory)] + [hashtable] + $Mapping, + + [Parameter(Mandatory)] + $RepoList, + + # LTS is not consider a package in this context. + # LTS is just another package name. + [Parameter(Mandatory)] + [ValidateSet('stable', 'preview')] + $Channel + ) + + $mappedReposUsedByPwsh = @() + foreach ($package in $Mapping.Packages) + { + Write-Verbose "package: $package" + $packageChannel = $package.channel + if (!$packageChannel) { + $packageChannel = 'all' + } + + Write-Verbose "package channel: $packageChannel" + if ($packageChannel -eq 'all' -or $packageChannel -eq $Channel) + { + $repoIds = [System.Collections.Generic.List[string]]::new() + $packageFormat = $package.PackageFormat + Write-Verbose "package format: $packageFormat" -Verbose + $extension = [System.io.path]::GetExtension($packageFormat) + $packageType = $extension -replace '^\.' + + if ($package.distribution.count -gt 1) { + throw "Package $($package | out-string) has more than one Distribution." + } + + foreach ($distribution in $package.distribution) + { + $urlGlob = $package.url + switch ($packageType) + { + 'deb' { + $urlGlob = $urlGlob + '-apt' + } + 'rpm' { + $urlGlob = $urlGlob + '-yum' + } + default { + throw "Unknown package type: $packageType" + } + } + + Write-Verbose "---Finding repo id for: $urlGlob---" -Verbose + $repos = $RepoList | Where-Object { $_.name -eq $urlGlob } + + if ($repos.id) { + Write-Verbose "Found repo id: $($repos.id)" -Verbose + $repoIds.AddRange(([string[]]$repos.id)) + } + else { + throw "Could not find repo for $urlGlob" + } + + if ($repoIds.Count -gt 0) { + $mappedReposUsedByPwsh += ($package + @{ "RepoId" = $repoIds.ToArray() }) + } + } + } + } + + Write-Verbose -Verbose "mapped repos length: $($mappedReposUsedByPwsh.Length)" + return $mappedReposUsedByPwsh +} + +<# +This function creates package objects for the packages to be published, +with the package name (ie package name format resolve with channel based PackageName and pwsh version), repoId, distribution and package path. +#> +function Get-PackageObjects() { + param( + [Parameter(Mandatory)] + [psobject[]] + $RepoObjects, + + [Parameter(Mandatory)] + [string] + $ReleaseVersion, + + [Parameter(Mandatory)] + [string[]] + $PackageName + ) + + $packages = @() + + foreach ($pkg in $RepoObjects) + { + if ($pkg.RepoId.count -gt 1) { + throw "Package $($pkg.name) has more than one repo id." + } + + if ($pkg.Distribution.count -gt 1) { + throw "Package $($pkg.name) has more than one Distribution." + } + + $pkgRepo = $pkg.RepoId | Select-Object -First 1 + $pkgDistribution = $pkg.Distribution | Select-Object -First 1 + + foreach ($name in $PackageName) { + $pkgName = $pkg.PackageFormat.Replace('PACKAGE_NAME', $name).Replace('POWERSHELL_RELEASE', $ReleaseVersion) + + if ($pkgName.EndsWith('.rpm')) { + $pkgName = $pkgName.Replace($ReleaseVersion, $ReleaseVersion.Replace('-', '_')) + } + + $packagePath = "$pwshPackagesFolder/$pkgName" + $packagePathExists = Test-Path -Path $packagePath + if (!$packagePathExists) + { + throw "package path $packagePath does not exist" + } + + Write-Verbose "Creating package info object for package '$pkgName' for repo '$pkgRepo'" + $packages += @{ + PackagePath = $packagePath + PackageName = $pkgName + RepoId = $pkgRepo + Distribution = $pkgDistribution + } + + Write-Verbose -Verbose "package info obj: Name: $pkgName RepoId: $pkgRepo Distribution: $pkgDistribution PackagePath: $packagePath" + } + } + + Write-Verbose -Verbose "count of packages objects: $($packages.Length)" + return $packages +} + +<# +This function stages, uploads and publishes the powershell packages to their associated repositories in PMC. +#> +function Publish-PackageToPMC() { + param( + [Parameter(Mandatory)] + [pscustomobject[]] + $PackageObject, + + [Parameter(Mandatory)] + [string] + $ConfigPath, + + [Parameter(Mandatory)] + [bool] + $SkipPublish + ) + + # Don't fail outright when an error occurs, but instead pool them until + # after attempting to publish every package. That way we can choose to + # proceed for a partial failure. + $errorMessage = [System.Collections.Generic.List[string]]::new() + foreach ($finalPackage in $PackageObject) + { + Write-Verbose "---Staging package: $($finalPackage.PackageName)---" -Verbose + $packagePath = $finalPackage.PackagePath + $pkgRepo = $finalPackage.RepoId + + $extension = [System.io.path]::GetExtension($packagePath) + $packageType = $extension -replace '^\.' + Write-Verbose "packageType: $packageType" -Verbose + + $packageListJson = pmc --config $ConfigPath package $packageType list --file $packagePath + $list = $packageListJson | ConvertFrom-Json + + $packageId = @() + if ($list.count -ne 0) + { + Write-Verbose "Package '$packagePath' already exists, skipping upload" -Verbose + $packageId = $list.results.id | Select-Object -First 1 + } + else { + # PMC UPLOAD COMMAND + Write-Verbose -Verbose "Uploading package, config: '$ConfigPath' package: '$packagePath'" + $uploadResult = $null + try { + $uploadResult = pmc --config $ConfigPath package upload $packagePath --type $packageType + } + catch { + $errorMessage.Add("Uploading package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $packageId = ($uploadResult | ConvertFrom-Json).id + } + + Write-Verbose "Got package ID: '$packageId'" -Verbose + $distribution = $finalPackage.Distribution | select-object -First 1 + Write-Verbose "distribution: $distribution" -Verbose + + if (!$SkipPublish) + { + Write-Verbose "---Publishing package: $($finalPackage.PackageName) to $pkgRepo---" -Verbose + + if (($packageType -ne 'rpm') -and ($packageType -ne 'deb')) + { + throw "Unsupported package type: $packageType" + return 1 + } + else { + # PMC UPDATE COMMAND + $rawUpdateResponse = $null + try { + if ($packageType -eq 'rpm') { + $rawUpdateResponse = pmc --config $ConfigPath repo package update $pkgRepo --add-packages $packageId + } elseif ($packageType -eq 'deb') { + $rawUpdateResponse = pmc --config $ConfigPath repo package update $pkgRepo $distribution --add-packages $packageId + } + } + catch { + $errorMessage.Add("Invoking update for package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $state = ($rawUpdateResponse | ConvertFrom-Json).state + Write-Verbose -Verbose "update response state: $state" + if ($state -ne 'completed') { + $errorMessage.Add("Publishing package $($finalPackage.PackageName) to $pkgRepo failed: $rawUpdateResponse") + continue + } + } + + # PMC PUBLISH COMMAND + # The CLI outputs messages and JSON in the same stream, so we must sift through it for now + # This is planned to be fixed with a switch in a later release + Write-Verbose -Verbose ([pscustomobject]($package + @{ + PackageId = $packageId + })) + + # At this point, the changes are staged and will eventually be publish. + # Running publish, causes them to go live "immediately" + $rawPublishResponse = $null + try { + $rawPublishResponse = pmc --config $ConfigPath repo publish $pkgRepo + } + catch { + $errorMessage.Add("Invoking final publish for package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $publishState = ($rawPublishResponse | ConvertFrom-Json).state + Write-Verbose -Verbose "publish response state: $publishState" + if ($publishState -ne 'completed') { + $errorMessage.Add("Final publishing of package $($finalPackage.PackageName) to $pkgRepo failed: $rawPublishResponse") + continue + } + } else { + Write-Verbose -Verbose "Skipping Uploading package --config-file '$ConfigPath' package add '$packagePath' --repoID '$pkgRepo'" + } + } + + if ($errorMessage) { + throw $errorMessage -join [Environment]::NewLine + } +} + +if ($null -eq $env:MAPPING_FILE) +{ + Write-Verbose -Verbose "MAPPING_FILE variable didn't get passed correctly" + return 1 +} + +if ($null -eq $env:PWSH_PACKAGES_TARGZIP) +{ + Write-Verbose -Verbose "PWSH_PACKAGES_TARGZIP variable didn't get passed correctly" + return 1 +} + +if ($null -eq $env:PMC_METADATA) +{ + Write-Verbose -Verbose "PMC_METADATA variable didn't get passed correctly" + return 1 +} + +try { + Write-Verbose -Verbose "Downloading files" + Invoke-WebRequest -Uri $env:MAPPING_FILE -OutFile mapping.json + Invoke-WebRequest -Uri $env:PWSH_PACKAGES_TARGZIP -OutFile packages.tar.gz + Invoke-WebRequest -Uri $env:PMC_METADATA -OutFile pmcMetadata.json + + # create variables to those paths and test them + $mappingFilePath = Join-Path "/package/unarchive/" -ChildPath "mapping.json" + $mappingFilePathExists = Test-Path $mappingFilePath + if (!$mappingFilePathExists) + { + Write-Verbose -Verbose "mapping.json expected at $mappingFilePath does not exist" + return 1 + } + + $packagesTarPath = Join-Path -Path "/package/unarchive/" -ChildPath "packages.tar.gz" + $packagesTarPathExists = Test-Path $packagesTarPath + if (!$packagesTarPathExists) + { + Write-Verbose -Verbose "packages.tar.gz expected at $packagesTarPath does not exist" + return 1 + } + + # Extract files from 'packages.tar.gz' + Write-Verbose -Verbose "---Extracting files from packages.tar.gz---" + $pwshPackagesFolder = Join-Path -Path "/package/unarchive/" -ChildPath "packages" + New-Item -Path $pwshPackagesFolder -ItemType Directory + tar -xzvf $packagesTarPath -C $pwshPackagesFolder --force-local + Get-ChildItem $pwshPackagesFolder -Recurse + + $metadataFilePath = Join-Path -Path "/package/unarchive/" -ChildPath "pmcMetadata.json" + $metadataFilePathExists = Test-Path $metadataFilePath + if (!$metadataFilePathExists) + { + Write-Verbose -Verbose "pmcMetadata.json expected at $metadataFilePath does not exist" + return 1 + } + + # files in the extracted Run dir + $configPath = Join-Path '/package/unarchive/Run' -ChildPath 'settings.toml' + $configPathExists = Test-Path -Path $configPath + if (!$configPathExists) + { + Write-Verbose -Verbose "settings.toml expected at $configPath does not exist" + return 1 + } + + $pythonDlFolder = Join-Path '/package/unarchive/Run' -ChildPath 'python_dl' + $pyPathExists = Test-Path -Path $pythonDlFolder + if (!$pyPathExists) + { + Write-Verbose -Verbose "python_dl expected at $pythonDlFolder does not exist" + return 1 + } + + Write-Verbose -Verbose "Installing pmc-cli" + pip install --upgrade pip + pip --version --verbose + pip install /package/unarchive/Run/python_dl/*.whl + + # Get metadata + $channel = "" + $packageNames = @() + $metadataContent = Get-Content -Path $metadataFilePath | ConvertFrom-Json + $releaseVersion = $metadataContent.ReleaseTag.TrimStart('v') + $skipPublish = $metadataContent.SkipPublish + $lts = $metadataContent.LTS + + # Check if this is a rebuild version (e.g., 7.4.13-rebuild.5) + $isRebuild = $releaseVersion -match '-rebuild\.' + + if ($releaseVersion.Contains('-')) { + $channel = 'preview' + $packageNames = @('powershell-preview') + } + else { + $channel = 'stable' + $packageNames = @('powershell') + } + + # Only add LTS package if not a rebuild branch + if ($lts -and -not $isRebuild) { + $packageNames += @('powershell-lts') + } + + Write-Verbose -Verbose "---Getting repository list---" + $rawResponse = pmc --config $configPath repo list --limit 800 + $response = $rawResponse | ConvertFrom-Json + $limit = $($response.limit) + $count = $($response.count) + Write-Verbose -Verbose "'pmc repo list' limit is: $limit and count is: $count" + $repoList = $response.results + + Write-Verbose -Verbose "---Getting package info---" + + + Write-Verbose "Reading mapping file from '$mappingFilePath'" -Verbose + $mapping = Get-Content -Raw -LiteralPath $mappingFilePath | ConvertFrom-Json -AsHashtable + $mappedReposUsedByPwsh = Get-MappedRepositoryIds -Mapping $mapping -RepoList $repoList -Channel $channel + $packageObjects = Get-PackageObjects -RepoObjects $mappedReposUsedByPwsh -PackageName $packageNames -ReleaseVersion $releaseVersion + Write-Verbose -Verbose "skip publish $skipPublish" + Publish-PackageToPMC -PackageObject $packageObjects -ConfigPath $configPath -SkipPublish $skipPublish +} +catch { + Write-Error -ErrorAction Stop $_.Exception.Message + return 1 +} + +return 0 diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json b/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json new file mode 100644 index 00000000000..d7c75c2e216 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/rolloutParameters.json", + "contentVersion": "1.0.0.0", + "shellExtensions": [ + { + "name": "Run", + "type": "Run", + "properties": { + "maxExecutionTime": "PT2H" + }, + "package": { + "reference": { + "path": "Shell/Run.tar" + } + }, + "launch": { + "command": [ + "/bin/bash", + "-c", + "pwsh ./Run/Run.ps1" + ], + "environmentVariables": [ + { + "name": "MAPPING_FILE", + "reference": + { + "path": "Parameters\\mapping.json" + } + }, + { + "name": "PWSH_PACKAGES_TARGZIP", + "reference": + { + "path": "Parameters\\packages.tar.gz" + } + }, + { + "name": "PMC_METADATA", + "reference": + { + "path": "Parameters\\pmcMetadata.json" + } + } + ], + "identity": { + "type": "userAssigned", + "userAssignedIdentities": [ + "default" + ] + } + } + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt b/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt new file mode 100644 index 00000000000..7dea76edb3d --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt @@ -0,0 +1 @@ +1.0.1 diff --git a/.pipelines/MSIXBundle-vPack-Official.yml b/.pipelines/MSIXBundle-vPack-Official.yml new file mode 100644 index 00000000000..2461d3cd310 --- /dev/null +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -0,0 +1,414 @@ +trigger: none +pr: none + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'createVPack' + displayName: 'Create and Submit VPack' + type: boolean + default: true +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: msixbundle_vPack_$(Build.SourceBranchName)_Prod.True_Create.${{ parameters.createVPack }}_$(date:yyyyMMdd).$(rev:rr) + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: netiso + value: ${{ parameters.netiso }} + - group: certificate_logical_to_actual # used within signing task + - group: MSIXSigningProfile + - group: msixTools + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/Microsoft.Official.yml@onebranchTemplates + parameters: + platform: + name: 'windows_undocked' # windows undocked + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + cloudvault: + enabled: false + globalSdl: + useCustomPolicy: true # for signing code + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + + stages: + - stage: Build_MSIX_Package + displayName: 'Build and create MSIX packages' + dependsOn: [] + jobs: + - job: Build + pool: + type: windows + + strategy: + matrix: + x64: + Architecture: x64 + arm64: + Architecture: arm64 + + variables: + ArtifactPlatform: 'windows' + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: drop_build_$(Architecture) + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + # The env variable 'ReleaseTagVar' will be updated in this step. + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $releaseTag = '$(ReleaseTagVar)' + if ($releaseTag -match '-') { + throw "Never release msixbundle vpack for a preview build. Current version: $releaseTag" + } + + # Check if release tag matches the expected format v#.#.# + $matched = $releaseTag -match '^v\d+\.(\d+)\.\d+$' + if (-not $matched) { + throw "Release tag must be in the format v#.#.#, such as 'v7.4.3'. Current version: $releaseTag" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + ### START BUILD ### + + # Clone the checked out PowerShell repo to '/PowerShell' and set the variable 'PowerShellRoot'. + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) + + # Add CodeQL Init task right before your 'Build' step. + - task: CodeQL3000Init@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + # AnalyzeInPipeline: false = upload results + # AnalyzeInPipeline: true = do not upload results + AnalyzeInPipeline: false + Language: csharp + + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + $runtime = switch ($env:Architecture) + { + "x64" { "win7-x64" } + "arm64" { "win-arm64" } + } + + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Architecture) -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore -ReleaseTag $(ReleaseTagVar) + + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Verifying pdbs exist in build folder" + $pdbs = Get-ChildItem -Path $buildWithSymbolsPath -Recurse -Filter *.pdb + if ($pdbs.Count -eq 0) { + throw "No pdbs found in build folder" + } + else { + Write-Verbose -Verbose "Found $($pdbs.Count) pdbs in build folder" + $pdbs | ForEach-Object { + Write-Verbose -Verbose "Pdb: $($_.FullName)" + } + + $pdbs | Compress-Archive -DestinationPath '$(ob_outputDirectory)\symbols-$(Architecture).zip' -Update + } + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: 'Build Windows Universal - $(Architecture)-$(BuildConfiguration) Symbols folder' + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + # Add CodeQL Finalize task right after your 'Build' step. + - task: CodeQL3000Finalize@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(PowerShellRoot)\src' + ob_restore_phase: true + + # The signed files will be put in '$(ob_outputDirectory)\Signed-$(Runtime)' after this step. + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + OfficialBuild: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\Signed-$(Runtime)' -Recurse | Out-String -Width 9999 + displayName: Capture signed files + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -Width 9999 + displayName: Capture Environment + condition: succeededOrFailed() + + ### START Packaging ### + + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false + + - pwsh: | + Write-Verbose -Verbose "runtime = '$(Runtime)'" + Write-Verbose -Verbose "RepoRoot = '$(PowerShellRoot)'" + + $runtime = '$(Runtime)' + $repoRoot = '$(PowerShellRoot)' + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + Find-Dotnet + + $signedFilesPath = '$(ob_outputDirectory)\Signed-$(Runtime)' + $psoptionsFilePath = '$(ob_outputDirectory)\psoptions\psoptions.json' + + Write-Verbose -Verbose "signedFilesPath: $signedFilesPath" + Write-Verbose -Verbose "psoptionsFilePath: $psoptionsFilePath" + + Write-Verbose -Message "checking pwsh exists in $signedFilesPath" -Verbose + if (-not (Test-Path $signedFilesPath\pwsh.exe)) { + throw "pwsh.exe not found in $signedFilesPath" + } + + Write-Verbose -Message "Restoring PSOptions from $psoptionsFilePath" -Verbose + + Restore-PSOptions -PSOptionsPath "$psoptionsFilePath" + Get-PSOptions | Write-Verbose -Verbose + + $metadata = Get-Content "$repoRoot\tools\metadata.json" -Raw | ConvertFrom-Json + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + $publishLTS = $metadata.LTSRelease.PublishToChannels + $publishStable = $metadata.StableRelease.PublishToChannels + + Write-Verbose -Verbose "Publish LTS: $publishLTS" + Write-Verbose -Verbose "Publish Stable: $publishStable" + + if (-not $publishLTS -and -not $publishStable) { + throw "metadata.json indicates no channels to publish to." + } + + ## Generated packages are placed in the current directory by default. + Set-Location $repoRoot + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $runtime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$publishLTS + + if ($publishLTS -and $publishStable) { + $enabledChannels = "LTS,Stable" + Write-Verbose -Verbose "Publish to both LTS and Stable channels. Building additional Stable MSIX." + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $runtime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + } + + $msixPkgNameFilter = "PowerShell*.msix" + $msixPkgFile = Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -Recurse -File | ForEach-Object FullName + Write-Verbose -Verbose "Unsigned msix package(s): $msixPkgFile" + + $pkgDir = '$(ob_outputDirectory)\pkgs' + $null = New-Item -ItemType Directory -Path $pkgDir -Force + Copy-Item -Path $msixPkgFile -Destination $pkgDir -Force -Verbose + + if (-not $enabledChannels) { + $enabledChannels = $publishLTS ? 'LTS' : ($publishStable ? 'Stable' : 'None') + } + + ## Create an output variable for the enabled channels so that downstream stages can use it. + $vstsCommandString = "vso[task.setvariable variable=EnabledChannels;isOutput=true]$enabledChannels" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + name: BuildMSIXPackage + displayName: 'Build MSIX Package (Unsigned)' + + ### END OF Packaging ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\pkgs' -Recurse + displayName: 'List Unsigned Package' + + - pwsh: | + $signedFilesPath = '$(ob_outputDirectory)\Signed-$(Runtime)' + Remove-Item -Path $signedFilesPath -Recurse -Force -Verbose + displayName: 'Remove Signed-$(Runtime) folder' + + - stage: Pack_MSIXBundle_And_Sign + displayName: 'Pack and sign MSIXBundle' + dependsOn: [Build_MSIX_Package] + + variables: + EnabledChannels: $[ stageDependencies.Build_MSIX_Package.Build.outputs['x64.BuildMSIXPackage.EnabledChannels'] ] + + jobs: + - template: /.pipelines/templates/create-msixbundle-vpack.yml@self + parameters: + Channel: 'LTS' + createVPack: ${{ parameters.createVPack }} + + - template: /.pipelines/templates/create-msixbundle-vpack.yml@self + parameters: + Channel: 'Stable' + createVPack: ${{ parameters.createVPack }} + + - stage: Publish_Symbols + displayName: 'Publish Symbols' + dependsOn: [Pack_MSIXBundle_And_Sign] + jobs: + - job: PublishSymbols + pool: + type: windows + variables: + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem Env: | Out-String -Width 9999 + displayName: 'Capture Environment Variables' + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for arm64 + + - pwsh: | + $downloadDir = '$(Build.ArtifactStagingDirectory)\downloads' + Write-Verbose -Verbose "Enumerating $downloadDir" + $downloadedArtifacts = Get-ChildItem -Path $downloadDir -Recurse -Filter 'symbols-*.zip' + $downloadedArtifacts | Out-String -Width 9999 + + $expandedRoot = New-Item -Path "$(Pipeline.Workspace)\expanded" -ItemType Directory -Verbose + $downloadedArtifacts | ForEach-Object { + $expandDir = Join-Path $expandedRoot $_.BaseName + Write-Verbose -Verbose "Expanding $($_.FullName) to $expandDir" + $null = New-Item -Path $expandDir -ItemType Directory -Verbose + Expand-Archive -Path $_.FullName -DestinationPath $expandDir -Force + } + + Write-Verbose -Verbose "Enumerating $expandedRoot" + Get-ChildItem -Path $expandedRoot -Recurse | Out-String -Width 9999 + $vstsCommandString = "vso[task.setvariable variable=SymbolsPath]$expandedRoot" + Write-Verbose -Message "$vstsCommandString" -Verbose + Write-Host -Object "##$vstsCommandString" + displayName: Expand and capture symbols folders + + - task: PublishSymbols@2 + condition: and(succeeded(), ${{ parameters.createVPack }}) + inputs: + symbolsFolder: '$(SymbolsPath)' + searchPattern: '**/*.pdb' + indexSources: false + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true diff --git a/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml new file mode 100644 index 00000000000..69506750c34 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml @@ -0,0 +1,98 @@ +trigger: none + +parameters: + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Debugging - Skip Signing + type: string + default: 'NO' + - name: RUN_TEST_AND_RELEASE + displayName: Debugging - Run Test and Release Artifacts Stage + type: boolean + default: true + - name: RUN_WINDOWS + displayName: Debugging - Enable Windows Stage + type: boolean + default: true + - name: ENABLE_MSBUILD_BINLOGS + displayName: Debugging - Enable MSBuild Binary Logs + type: boolean + default: false + - name: FORCE_CODEQL + displayName: Debugging - Enable CodeQL and set cadence to 1 hour + type: boolean + default: false + +name: bins-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + ref: master + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +variables: + - template: /.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml@self + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + customTags: 'ES365AIMigrationTooling' + featureFlags: + LinuxHostVersion: + Network: KS3 + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + codeql: + compiled: + enabled: $(CODEQL_ENABLED) + tsaEnabled: true # This enables TSA bug filing only for CodeQL 3000 + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: /.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml@self + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml new file mode 100644 index 00000000000..0993cd69546 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml @@ -0,0 +1,97 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: ForceAzureBlobDelete + displayName: Delete Azure Blob + type: string + values: + - true + - false + default: false + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: disableNetworkIsolation + type: boolean + default: false + +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} + +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + cloudvault: + enabled: false + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + LinuxHostVersion: + Network: KS3 + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + disableNetworkIsolation: ${{ variables.disableNetworkIsolation }} + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml new file mode 100644 index 00000000000..b0bb4d79b39 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml @@ -0,0 +1,82 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: skipPublish + displayName: Skip PMC Publish + type: boolean + default: false + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + +name: ev2-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: Netlock + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + tsaOptionsFile: $(Build.SourcesDirectory)\.config\tsaoptions.json + stages: + - template: /.pipelines/templates/release-prep-for-ev2.yml@self + parameters: + skipPublish: ${{ parameters.skipPublish }} + + # NonOfficial: run the publish stage to verify templateContext artifact download, + # but skip the actual Ev2 push to PMC. + - template: /.pipelines/templates/release-publish-pmc.yml@self + parameters: + releaseEnvironment: Test + stagePrefix: Test + skipEv2Push: true diff --git a/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml new file mode 100644 index 00000000000..ebfc599e42a --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml @@ -0,0 +1,106 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: SkipPublish + displayName: Skip Publishing to Nuget + type: boolean + default: false + - name: SkipPSInfraInstallers + displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location + type: boolean + default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false + +name: release-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Release-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + - repository: PSInternalTools + type: git + name: PowerShellCore/Internal-PowerShellTeam-Tools + ref: refs/heads/master + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + # NOTE: The alias name "PSPackagesOfficial" is intentionally reused here even + # for the NonOfficial pipeline source. Downstream shared templates (for example, + # release-validate-sdk.yml and release-upload-buildinfo.yml) reference artifacts + # using `download: PSPackagesOfficial`, so changing this alias would break them. + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + release: + category: NonAzure + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + # suppression: + # suppressionFile: $(Build.SourcesDirectory)\.gdn\global.gdnsuppress + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: /.pipelines/templates/stages/PowerShell-Release-Stages.yml@self + parameters: + releaseEnvironment: Test + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml new file mode 100644 index 00000000000..071db02cff8 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml @@ -0,0 +1,88 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'createVPack' + displayName: 'Create and Submit VPack' + type: boolean + default: true +- name: vPackName + type: string + displayName: 'VPack Name:' + default: 'PowerShell.BuildTool' + values: + - PowerShell.BuildTool + - PowerShell + - PowerShellDoNotUse +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: vPack_$(Build.SourceBranchName)_NonOfficial_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) + +variables: + - template: /.pipelines/templates/variables/PowerShell-vPack-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/Microsoft.NonOfficial.yml@onebranchTemplates + parameters: + platform: + name: 'windows_undocked' # windows undocked + + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + + cloudvault: + enabled: false + + globalSdl: + useCustomPolicy: true # for signing code + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + stages: + - template: /.pipelines/templates/stages/PowerShell-vPack-Stages.yml@self + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml new file mode 100644 index 00000000000..82f129a0a5e --- /dev/null +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -0,0 +1,98 @@ +trigger: none + +parameters: + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Debugging - Skip Signing + type: string + default: 'NO' + - name: RUN_TEST_AND_RELEASE + displayName: Debugging - Run Test and Release Artifacts Stage + type: boolean + default: true + - name: RUN_WINDOWS + displayName: Debugging - Enable Windows Stage + type: boolean + default: true + - name: ENABLE_MSBUILD_BINLOGS + displayName: Debugging - Enable MSBuild Binary Logs + type: boolean + default: false + - name: FORCE_CODEQL + displayName: Debugging - Enable CodeQL and set cadence to 1 hour + type: boolean + default: false + +name: bins-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + ref: master + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +variables: + - template: templates/variables/PowerShell-Coordinated_Packages-Variables.yml + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + parameters: + customTags: 'ES365AIMigrationTooling' + featureFlags: + LinuxHostVersion: + Network: KS3 + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + codeql: + compiled: + enabled: $(CODEQL_ENABLED) + tsaEnabled: true # This enables TSA bug filing only for CodeQL 3000 + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: templates/stages/PowerShell-Coordinated_Packages-Stages.yml + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: true diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml new file mode 100644 index 00000000000..8afce29ede7 --- /dev/null +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -0,0 +1,97 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: ForceAzureBlobDelete + displayName: Delete Azure Blob + type: string + values: + - true + - false + default: false + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: disableNetworkIsolation + type: boolean + default: false + +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) + +variables: + - template: templates/variables/PowerShell-Packages-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} + +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' + trigger: + branches: + include: + - master + - releases/* + + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + parameters: + cloudvault: + enabled: false + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + LinuxHostVersion: + Network: KS3 + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + disableNetworkIsolation: ${{ variables.disableNetworkIsolation }} + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + stages: + - template: templates/stages/PowerShell-Packages-Stages.yml + parameters: + OfficialBuild: true diff --git a/.pipelines/PowerShell-Release-Official-Azure.yml b/.pipelines/PowerShell-Release-Official-Azure.yml new file mode 100644 index 00000000000..b5f57438925 --- /dev/null +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -0,0 +1,76 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: skipPublish + displayName: Skip PMC Publish + type: boolean + default: false + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + +name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) + +variables: + - template: templates/variables/PowerShell-Release-Azure-Variables.yml + parameters: + debug: ${{ parameters.debug }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-Official' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: Netlock + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + tsaOptionsFile: $(Build.SourcesDirectory)\.config\tsaoptions.json + stages: + - template: /.pipelines/templates/release-prep-for-ev2.yml@self + parameters: + skipPublish: ${{ parameters.skipPublish }} + + - template: /.pipelines/templates/release-publish-pmc.yml@self diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml new file mode 100644 index 00000000000..3528e6b1471 --- /dev/null +++ b/.pipelines/PowerShell-Release-Official.yml @@ -0,0 +1,102 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: SkipPublish + displayName: Skip Publishing to Nuget + type: boolean + default: false + - name: SkipPSInfraInstallers + displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location + type: boolean + default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false + +name: release-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) + +variables: + - template: templates/variables/PowerShell-Release-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + - repository: PSInternalTools + type: git + name: PowerShellCore/Internal-PowerShellTeam-Tools + ref: refs/heads/master + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-Official' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + parameters: + release: + category: NonAzure + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + # suppression: + # suppressionFile: $(Build.SourcesDirectory)\.gdn\global.gdnsuppress + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: templates/stages/PowerShell-Release-Stages.yml + parameters: + releaseEnvironment: Production + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml new file mode 100644 index 00000000000..13087fbbf65 --- /dev/null +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -0,0 +1,88 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'createVPack' + displayName: 'Create and Submit VPack' + type: boolean + default: true +- name: vPackName + type: string + displayName: 'VPack Name:' + default: 'PowerShell.BuildTool' + values: + - PowerShell.BuildTool + - PowerShell + - PowerShellDoNotUse +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: vPack_$(Build.SourceBranchName)_Prod_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) + +variables: + - template: templates/variables/PowerShell-vPack-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/Microsoft.Official.yml@onebranchTemplates + parameters: + platform: + name: 'windows_undocked' # windows undocked + + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + + cloudvault: + enabled: false + + globalSdl: + useCustomPolicy: true # for signing code + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + stages: + - template: templates/stages/PowerShell-vPack-Stages.yml + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/apiscan-gen-notice.yml b/.pipelines/apiscan-gen-notice.yml new file mode 100644 index 00000000000..df5ebaac091 --- /dev/null +++ b/.pipelines/apiscan-gen-notice.yml @@ -0,0 +1,111 @@ +# 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 + +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: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - 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.6" # Default is build number + isLargeApp: false # Default: false. + symbolsFolder: $(SymbolsServerUrl);$(ob_outputDirectory) +#softwareFolder - relative path to a folder to be scanned. Default value is root of artifacts folder + 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: [] diff --git a/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep b/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.pipelines/store/PDP/PDP/en-US/PDP.xml b/.pipelines/store/PDP/PDP/en-US/PDP.xml new file mode 100644 index 00000000000..ce36a3677f7 --- /dev/null +++ b/.pipelines/store/PDP/PDP/en-US/PDP.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + Shell + + PowerShell + + Terminal + + Command Line + + Automation + + Task Automation + + Scripting + + + PowerShell is a task-based command-line shell and scripting language built on .NET. PowerShell helps system administrators and power-users rapidly automate task that manage operating systems (Linux, macOS, and Windows) and processes. + +PowerShell commands let you manage computers from the command line. PowerShell providers let you access data stores, such as the registry and certificate store, as easily as you access the file system. PowerShell includes a rich expression parser and a fully developed scripting language. + +PowerShell is Open Source. See https://github.com/powershell/powershell + + + + + + + + + + + + + + + + + + + + + + Please see our GitHub releases page for additional details. + + + + + + + + + + + + + + + + + + + + Interactive Shell + + Scripting Language + + Remote Management + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft Corporation + + + + + https://github.com/PowerShell/PowerShell + + https://github.com/PowerShell/PowerShell/issues + + https://go.microsoft.com/fwlink/?LinkID=521839 + diff --git a/.pipelines/store/SBConfig.json b/.pipelines/store/SBConfig.json new file mode 100644 index 00000000000..a52d60b045f --- /dev/null +++ b/.pipelines/store/SBConfig.json @@ -0,0 +1,69 @@ +{ + "helpUri": "https:\\\\aka.ms\\StoreBroker_Config", + "schemaVersion": 2, + "packageParameters": { + "PDPRootPath": "", + "Release": "", + "PDPInclude": [ + "PDP.xml" + ], + "PDPExclude": [], + "LanguageExclude": [ + "default", + "qps-ploc", + "qps-ploca", + "qps-plocm" + ], + "MediaRootPath": "", + "MediaFallbackLanguage": "en-US", + "PackagePath": [], + "OutPath": "", + "OutName": "", + "DisableAutoPackageNameFormatting": false + }, + "appSubmission": { + "productId": "", + "targetPublishMode": "Immediate", + "targetPublishDate": null, + "visibility": "NotSet", + "pricing": { + "priceId": "NotAvailable", + "trialPeriod": "NoFreeTrial", + "marketSpecificPricings": {}, + "sales": [] + }, + "allowTargetFutureDeviceFamilies": { + "Xbox": false, + "Team": false, + "Holographic": false, + "Desktop": false, + "Mobile": false + }, + "allowMicrosoftDecideAppAvailabilityToFutureDeviceFamilies": false, + "enterpriseLicensing": "None", + "applicationCategory": "NotSet", + "hardwarePreferences": [], + "hasExternalInAppProducts": false, + "meetAccessibilityGuidelines": false, + "canInstallOnRemovableMedia": false, + "automaticBackupEnabled": false, + "isGameDvrEnabled": false, + "gamingOptions": [ + { + "genres": [], + "isLocalMultiplayer": false, + "isLocalCooperative": false, + "isOnlineMultiplayer": false, + "isOnlineCooperative": false, + "localMultiplayerMinPlayers": 0, + "localMultiplayerMaxPlayers": 0, + "localCooperativeMinPlayers": 0, + "localCooperativeMaxPlayers": 0, + "isBroadcastingPrivilegeGranted": false, + "isCrossPlayEnabled": false, + "kinectDataForExternal": "Disabled" + } + ], + "notesForCertification": "" + } +} diff --git a/.pipelines/templates/SetVersionVariables.yml b/.pipelines/templates/SetVersionVariables.yml new file mode 100644 index 00000000000..30ed1704022 --- /dev/null +++ b/.pipelines/templates/SetVersionVariables.yml @@ -0,0 +1,48 @@ +parameters: +- name: ReleaseTagVar + default: v6.2.0 +- name: ReleaseTagVarName + default: ReleaseTagVar +- name: CreateJson + default: 'no' +- name: ob_restore_phase + type: boolean + default: true + +steps: +- template: set-reporoot.yml@self + parameters: + ob_restore_phase: ${{ parameters.ob_restore_phase }} + +- 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: ${{ parameters.ob_restore_phase }} + +- powershell: | + Get-ChildItem -Path Env: | Out-String -Width 150 + displayName: Capture environment + condition: succeededOrFailed() + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} 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/channelSelection.yml b/.pipelines/templates/channelSelection.yml new file mode 100644 index 00000000000..d6ddb53256e --- /dev/null +++ b/.pipelines/templates/channelSelection.yml @@ -0,0 +1,49 @@ +steps: +- pwsh: | + # Determine LTS, Preview, or Stable + $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json + + $LTS = $metadata.LTSRelease.PublishToChannels + $Stable = $metadata.StableRelease.PublishToChannels + $isPreview = '$(OutputReleaseTag.releaseTag)' -match '-' + $releaseTag = '$(OutputReleaseTag.releaseTag)' + + # Rebuild branches should be treated as preview builds + # NOTE: The following regex is duplicated from rebuild-branch-check.yml. + # This duplication is necessary because channelSelection.yml does not call rebuild-branch-check.yml, + # and is used in contexts where that check may not have run. + # If you update this regex, also update it in rebuild-branch-check.yml to keep them in sync. + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + + # If this is a rebuild branch, force preview mode and ignore LTS metadata + if ($isRebuildBranch) { + $IsLTS = $false + $IsStable = $false + $IsPreview = $true + Write-Verbose -Message "Rebuild branch detected, forcing Preview channel" -Verbose + } + else { + $IsLTS = [bool]$LTS + $IsStable = [bool]$Stable + $IsPreview = [bool]$isPreview + } + + $channelVars = @{ + IsLTS = $IsLTS + IsStable = $IsStable + IsPreview = $IsPreview + } + + $trueCount = ($channelVars.Values | Where-Object { $_ }) | Measure-Object | Select-Object -ExpandProperty Count + if ($trueCount -gt 1) { + Write-Error "Only one of IsLTS, IsStable, or IsPreview can be true. Current values: IsLTS=$IsLTS, IsStable=$IsStable, IsPreview=$IsPreview" + exit 1 + } + + foreach ($name in $channelVars.Keys) { + $value = if ($channelVars[$name]) { 'true' } else { 'false' } + Write-Verbose -Message "Setting $name variable: $value" -Verbose + Write-Host "##vso[task.setvariable variable=$name;isOutput=true]$value" + } + name: ChannelSelection + displayName: Select Preview, Stable, or LTS Channel diff --git a/.pipelines/templates/checkAzureContainer.yml b/.pipelines/templates/checkAzureContainer.yml new file mode 100644 index 00000000000..3e383d2c572 --- /dev/null +++ b/.pipelines/templates/checkAzureContainer.yml @@ -0,0 +1,86 @@ +jobs: +- job: DeleteBlob + variables: + - group: Azure Blob variable group + - group: AzureBlobServiceConnection + - name: ob_artifactBaseName + value: BuildInfoJson + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_sbom_enabled + 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 + + - 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..b060c713683 --- /dev/null +++ b/.pipelines/templates/cloneToOfficialPath.yml @@ -0,0 +1,31 @@ +parameters: +- name: nativePathRoot + default: '' +- name: ob_restore_phase + type: boolean + default: true + +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." + } + # REPOROOT must be set by the pipeline - this is where the repository was checked out + $sourceDir = $env:REPOROOT + if (-not $sourceDir) { throw "REPOROOT environment variable is not set. This step depends on REPOROOT being configured in the pipeline." } + + $buildModulePath = Join-Path $sourceDir "build.psm1" + if (-not (Test-Path $buildModulePath)) { throw "build.psm1 not found at: $buildModulePath. REPOROOT must point to the PowerShell repository root." } + + Write-Verbose -Verbose -Message "Cloning from: $sourceDir to $nativePath" + git clone --quiet $sourceDir $nativePath + displayName: Clone PowerShell Repo to /PowerShell + errorActionPreference: silentlycontinue + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/compliance/apiscan.yml b/.pipelines/templates/compliance/apiscan.yml new file mode 100644 index 00000000000..b5a15699026 --- /dev/null +++ b/.pipelines/templates/compliance/apiscan.yml @@ -0,0 +1,170 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +jobs: + - job: APIScan + variables: + - 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: 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: 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: 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 + } + + $Destination = '$(ob_outputDirectory)' + if (-not (Test-Path $Destination)) { + Write-Verbose -Verbose -Message "Creating destination folder '$Destination'" + $null = mkdir $Destination + } + + Copy-Item -Path "$OutputFolder\*" -Destination $Destination -Recurse -Verbose + workingDirectory: '$(repoRoot)' + displayName: 'Build PowerShell Source' + + - pwsh: | + # Only keep windows runtimes + Write-Verbose -Verbose -Message "Deleting non-win-x64 runtimes ..." + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' | Where-Object {$_.FullName -notmatch '.*\\runtimes\\win'} | Foreach-Object { + Write-Verbose -Verbose -Message "Deleting $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force + } + + # Remove win-x86/arm/arm64 runtimes due to issues with those runtimes + Write-Verbose -Verbose -Message "Temporarily deleting win-x86/arm/arm64 runtimes ..." + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' | Where-Object {$_.FullName -match '.*\\runtimes\\win-(x86|arm)'} | Foreach-Object { + Write-Verbose -Verbose -Message "Deleting $($_.FullName)" + Remove-Item -Path $_.FullName -Recurse -Force + } + + Write-Host + Write-Verbose -Verbose -Message "Show content in 'runtimes' folder:" + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes' + Write-Host + workingDirectory: '$(repoRoot)' + displayName: 'Remove unused runtimes' + + - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. + displayName: 🔏 CodeQL 3000 Finalize + condition: eq(variables['CODEQL_ENABLED'], 'true') + + - 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..aec44b9b8f6 --- /dev/null +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -0,0 +1,112 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +parameters: + - name: parentJobs + type: jobList + +jobs: +- job: generateNotice + variables: + - 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\cgmanifest\tpn' + + - 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/create-msixbundle-vpack.yml b/.pipelines/templates/create-msixbundle-vpack.yml new file mode 100644 index 00000000000..df46523675f --- /dev/null +++ b/.pipelines/templates/create-msixbundle-vpack.yml @@ -0,0 +1,178 @@ +parameters: + - name: Channel + type: string + - name: createVPack + type: boolean + +jobs: +- job: Bundle_${{ parameters.Channel }} + condition: contains(variables['EnabledChannels'], '${{ parameters.Channel }}') + pool: + type: windows + + variables: + ArtifactPlatform: 'windows' + Channel: ${{ parameters.Channel }} + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: 'drop_pack_$(Channel)' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_packagename: 'PowerShell7-$(Channel).Store.app' + ob_createvpack_owneralias: 'dongbow' + ob_createvpack_description: 'VPack for the PowerShell 7 Store Application ($(Channel))' + ob_createvpack_targetDestinationDirectory: '$(Destination)' ## The value is from the 'CreateVpack' task, used when pulling the generated VPack. + ob_createvpack_propsFile: false + ob_createvpack_provData: true + ob_createvpack_metadata: '$(Build.SourceVersion)' + ob_createvpack_versionAs: string + ob_createvpack_version: '$(Version)' + ob_createvpack_verbose: true + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + + - template: /.pipelines/templates/shouldSign.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for arm64 + + # Finds the makeappx tool on the machine. + - pwsh: | + Write-Verbose -Verbose 'PowerShell Version: $(Version)' + $cmd = Get-Command makeappx.exe -ErrorAction Ignore + if ($cmd) { + Write-Verbose -Verbose 'makeappx available in PATH' + $exePath = $cmd.Source + } else { + $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | + Where-Object { $_.DirectoryName -match 'x64' } | + Select-Object -Last 1 + $exePath = $makeappx.FullName + Write-Verbose -Verbose "makeappx was found: $exePath" + } + $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Find makeappx tool + retryCountOnTaskFailure: 1 + + - pwsh: | + $sourceDir = '$(Pipeline.Workspace)\releasePipeline\msix' + $null = New-Item -Path $sourceDir -ItemType Directory -Force + + $channel = '$(Channel)' + if ($channel -eq 'LTS') { + Write-Verbose -Verbose "LTS channel. Remove Stable MSIX packages" + $stablePkgs = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | + Where-Object { $_.FullName -notlike '*-LTS-*.msix' } | ForEach-Object FullName + + if ($stablePkgs) { + Remove-Item -Path $stablePkgs -Force -Verbose -ErrorAction Stop + } else { + Write-Verbose -Verbose "No Stable MSIX package was found." + } + } + else { + Write-Verbose -Verbose "Stable channel. Remove LTS MSIX packages" + $ltsPkgs = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | + Where-Object { $_.FullName -like '*-LTS-*.msix' } | ForEach-Object FullName + + if ($ltsPkgs) { + Remove-Item -Path $ltsPkgs -Force -Verbose -ErrorAction Stop + } else { + Write-Verbose -Verbose "No LTS MSIX package was found." + } + } + + $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse + foreach ($msixFile in $msixFiles) { + $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose + } + + $file = Get-ChildItem $sourceDir | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $pkgName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating $pkgName" + + $makeappx = '$(MakeAppxPath)' + $outputDir = "$sourceDir\output" + New-Item $outputDir -Type Directory -Force > $null + & $makeappx bundle /d $sourceDir /p "$outputDir\$pkgName" + if ($LASTEXITCODE -ne 0) { + throw "makeappx bundle failed with exit code $LASTEXITCODE" + } + + Get-ChildItem -Path $sourceDir -Recurse | Out-String -Width 200 + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Create MsixBundle + retryCountOnTaskFailure: 1 + + - task: onebranch.pipeline.signing@1 + displayName: Sign MsixBundle + inputs: + command: 'sign' + signing_profile: $(MSIXProfile) + files_to_sign: '**/*.msixbundle' + search_root: '$(BundleDir)' + + - pwsh: | + $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File + Write-Verbose -Verbose "Signed bundle: $signedBundle" + + $signature = Get-AuthenticodeSignature -FilePath $signedBundle.FullName + if ($signature.Status -ne 'Valid') { + throw "The bundle file doesn't have a valid signature. Signature status: $($signature.Status)" + } + + if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { + $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop + } + + $channel = '$(Channel)' + $targetFileName = if ($channel -eq 'LTS') { + 'Microsoft.PowerShell-LTS_8wekyb3d8bbwe.msixbundle' + } else { + 'Microsoft.PowerShell_8wekyb3d8bbwe.msixbundle' + } + $targetPath = Join-Path '$(ob_outputDirectory)' $targetFileName + Copy-Item -Verbose -Path $signedBundle.FullName -Destination $targetPath + + Write-Verbose -Verbose "Uploaded Bundle:" + Get-ChildItem -Path $(ob_outputDirectory) | Out-String -Width 200 -Stream | Write-Verbose -Verbose + displayName: 'Stage msixbundle for VPack' + + - pwsh: | + Write-Verbose "VPack enabled: $(ob_createvpack_enabled)" -Verbose + Write-Verbose "VPack Name: $(ob_createvpack_packagename)" -Verbose + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + + $vpackFiles = Get-ChildItem -Path '$(ob_outputDirectory)\*' -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(ob_outputDirectory)" + } + $vpackFiles | Out-String -Width 200 + displayName: Debug Output Directory and Version diff --git a/.pipelines/templates/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/install-dotnet.yml b/.pipelines/templates/install-dotnet.yml new file mode 100644 index 00000000000..464e13d1047 --- /dev/null +++ b/.pipelines/templates/install-dotnet.yml @@ -0,0 +1,24 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: + - pwsh: | + if (-not (Test-Path '$(RepoRoot)')) { + $psRoot = '$(Build.SourcesDirectory)/PowerShell' + Set-Location $psRoot -Verbose + } + + $version = Get-Content ./global.json | ConvertFrom-Json | Select-Object -ExpandProperty sdk | Select-Object -ExpandProperty version + + Write-Verbose -Verbose "Installing .NET SDK with version $version" + + Import-Module ./build.psm1 -Force + Install-Dotnet -Version $version -Verbose + + displayName: 'Install dotnet SDK' + workingDirectory: $(RepoRoot) + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} + diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml new file mode 100644 index 00000000000..e37d8555533 --- /dev/null +++ b/.pipelines/templates/linux-package-build.yml @@ -0,0 +1,217 @@ +parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: 'deb' + +jobs: +- job: ${{ parameters.jobName }} + displayName: Package linux ${{ parameters.packageType }} + condition: succeeded() + pool: + type: linux + + variables: + - 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: 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 + # PGP signing profile selection: Mariner (Azure Linux) packages ship through + # a different distribution channel and must be signed with the Mariner release + # key; all other Linux packages use the standard PowerShell Linux key. Both + # key codes come from the `certificate_logical_to_actual` variable group. + - ${{ if startsWith(parameters.jobName, 'mariner') }}: + - name: SigningProfile + value: $(pgp_release_cert_id) + - ${{ else }}: + - name: SigningProfile + value: $(pgp_linux_cert_id) + + steps: + - checkout: self + 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: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - template: rebuild-branch-check.yml@self + + - 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 + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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 Both + + $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' } + 'deb-arm64' { 'Signed-linux-arm64', 'powershell*.deb' } + 'rpm-fxdependent' { 'Signed-fxdependent-linux-x64', 'powershell*.rpm' } + 'rpm-fxdependent-arm64' { 'Signed-fxdependent-linux-arm64', 'powershell*.rpm' } + 'rpm' { 'Signed-linux-x64', 'powershell*.rpm' } + '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 + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + $packageType = '$(PackageType)' + Write-Verbose -Verbose "packageType = $packageType" + + Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" -Verbose + Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + + $vstsCommandString = "vso[task.setvariable variable=PackageFilter]$pkgFilter" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + 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 + + if ($pkgPath -like '*.tar.gz') { + $entry = & tar -tzvf $pkgPath | Where-Object { $_ -match '\spwsh$' } | Select-Object -First 1 + if ($entry -notmatch '^-..x') { + throw "pwsh is not executable in $pkgPath : $entry" + } + } + displayName: 'Copy artifacts to output directory' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + + - 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..97b594830b3 --- /dev/null +++ b/.pipelines/templates/linux.yml @@ -0,0 +1,197 @@ +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: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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 + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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) + OfficialBuild: $(ps_official_build) + + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml new file mode 100644 index 00000000000..89dab90aa04 --- /dev/null +++ b/.pipelines/templates/mac-package-build.yml @@ -0,0 +1,296 @@ +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: 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_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: no + + - template: shouldSign.yml + + - template: cloneToOfficialPath.yml + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + - template: rebuild-branch-check.yml@self + + - 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: | + $signedDir = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/drop_macos_sign_${{ parameters.buildArchitecture }}/Signed-${{ parameters.buildArchitecture }}" + Get-ChildItem $signedDir -Recurse -Include 'pwsh', '*.dylib' | ForEach-Object { + codesign --verify --deep --strict --verbose=4 $_.FullName + if ($LASTEXITCODE -ne 0) { throw "codesign verification failed for $($_.FullName)" } + } + displayName: 'Verify Apple codesign on signed binaries' + + - pwsh: | + # Add -SkipReleaseChecks as a mitigation to unblock release. + # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. + + $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 + + if (-not (Test-Path "$repoRoot/tools/metadata.json")) { + throw "metadata.json not found in $repoRoot/tools" + } + + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" -Verbose + } + + Start-PSBootstrap -Scenario Package + + $macosRuntime = "osx-$buildArch" + + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + + if ($LTS) { + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + + $pkgNameFilter = "powershell-*$macosRuntime.pkg" + Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File + + foreach($p in $pkgPath) { + $file = $p.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } + + Start-PSPackage -Type tar -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS + $tarPkgNameFilter = "powershell-*$macosRuntime.tar.gz" + Write-Verbose -Verbose "Looking for tar packages with filter: $tarPkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File + + foreach($t in $tarPkgPath) { + $file = $t.FullName + $entry = & tar -tzvf $file | Where-Object { $_ -match '\spwsh$' } | Select-Object -First 1 + if ($entry -notmatch '^-..x') { + throw "pwsh is not executable in $file : $entry" + } + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } + + $packageInfo = Get-MacOSPackageIdentifierInfo -Version '$(Version)' -LTS:$LTS + Write-Verbose -Verbose "BundleId: $($packageInfo.PackageIdentifier)" + Write-Host "##vso[task.setvariable variable=BundleId;isOutput=true]$($packageInfo.PackageIdentifier)" + + displayName: 'Package ${{ parameters.buildArchitecture}}' + name: packageStep + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + +- job: sign_package_macOS_${{ parameters.buildArchitecture }} + displayName: Sign Package macOS ${{ parameters.buildArchitecture }} + dependsOn: package_macOS_${{ parameters.buildArchitecture }} + condition: succeeded() + pool: + type: windows + + variables: + - group: certificate_logical_to_actual + - 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 }} + - name: BundleId + value: $[ dependencies.package_macOS_${{ parameters.buildArchitecture }}.outputs['packageStep.BundleId'] ] + + 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": "$(apple_cert_id)", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "--options=runtime" + } + } + ] + + - task: onebranch.pipeline.signing@1 + displayName: 'OneBranch Notarize Package' + inputs: + command: 'sign' + files_to_sign: '**/*-osx-*.zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(apple_cert_id)", + "OperationCode": "MacAppNotarize", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "BundleId": "$(BundleId)" + } + } + ] + timeoutInMinutes: 120 + + - pwsh: | + $signedPkg = Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*osx*.zip" -File + + if (-not (Test-Path $(ob_outputDirectory))) { + $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory + } + + $expandDir = "$(Pipeline.Workspace)/pkgExpand" + $null = New-Item -Path $expandDir -ItemType Directory -Force + + $signedPkg | ForEach-Object { + Write-Verbose -Verbose "Signed package zip: $_" + Expand-Archive -Path $_ -DestinationPath $expandDir -Verbose + } + + # ESRP's signing pipeline nests the PKG inside a '.zip.unzipped' subfolder + $pkgFile = Get-ChildItem -Path $expandDir -Filter '*.pkg' -Recurse -File + if (-not $pkgFile) { + throw "Package not found in: $signedPkg" + } + + $pkgFile | ForEach-Object { + Move-Item -Path $_ -Destination $(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..cd492994617 --- /dev/null +++ b/.pipelines/templates/mac.yml @@ -0,0 +1,187 @@ +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: 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' + + ## We cross compile for arm64, so the arch is always x64 + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + Import-Module $(PowerShellRoot)/build.psm1 -Force + Start-PSBootstrap -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 + + $entitlements = "$(PowerShellRoot)/assets/macos-entitlements.plist" + $pwshBin = "$(OB_OUTPUTDIRECTORY)/pwsh" + Write-Verbose -Verbose "Applying entitlements to $pwshBin" + codesign --sign - --force --options runtime --entitlements $entitlements $pwshBin + if ($LASTEXITCODE -ne 0) { + throw "codesign failed with exit code $LASTEXITCODE" + } + + # Since we are using custom pool for macOS, we need to use artifact.upload to publish the artifacts + Write-Host "##vso[artifact.upload containerfolder=$artifactName;artifactname=$artifactName]$(OB_OUTPUTDIRECTORY)" + + $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) + OfficialBuild: $(ps_official_build) + + # Apple-sign the Mach-O binaries inside the signed output. + - pwsh: | + $signedDir = "$(ob_outputDirectory)/Signed-$(Runtime)" + $zipFile = "$(Pipeline.Workspace)/macho-$(BuildArchitecture).zip" + Compress-Archive -Path "$signedDir/*" -DestinationPath $zipFile -Force + displayName: Compress signed folder for Apple signing + + - task: onebranch.pipeline.signing@1 + displayName: Apple CodeSign Mach-O binaries + inputs: + command: 'sign' + files_to_sign: 'macho-$(BuildArchitecture).zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(apple_cert_id)", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "--options=runtime" + } + } + ] + + - pwsh: | + $signedDir = "$(ob_outputDirectory)/Signed-$(Runtime)" + $zipFile = "$(Pipeline.Workspace)/macho-$(BuildArchitecture).zip" + Expand-Archive -Path $zipFile -DestinationPath $signedDir -Force -Verbose + displayName: Expand Apple-signed Mach-O binaries into signed output + + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml new file mode 100644 index 00000000000..043c0b1f75c --- /dev/null +++ b/.pipelines/templates/nupkg.yml @@ -0,0 +1,299 @@ +jobs: +- job: build_nupkg + displayName: Package NuPkgs + condition: succeeded() + pool: + type: windows + + variables: + - 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 + - group: certificate_logical_to_actual + + 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: 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) + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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\net11.0\refint\$_.dll" + Write-Verbose -Verbose "RefAssembly: $refFile" + Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" + if (-not (Test-Path $refDoc)) { + Write-Warning "$refDoc not found" + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.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\net11.0\refint\*.dll" + Write-Verbose -Verbose 'RefAssembly: $refFile' + Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" + if (-not (Test-Path $refDoc)) { + Write-Warning "$refDoc not found" + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.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: '$(nuget_cert_id)' + 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: '$(nuget_cert_id)' + 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..cbe44ad0018 --- /dev/null +++ b/.pipelines/templates/obp-file-signing.yml @@ -0,0 +1,175 @@ +parameters: + binPath: '$(ob_outputDirectory)' + globalTool: 'false' + SigningProfile: 'external_distribution' + OfficialBuild: true + vPackScenario: 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: ${{ parameters.SigningProfile }} + 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" + + $officialBuild = [System.Convert]::ToBoolean('${{ parameters.OfficialBuild }}') + ## copy all files to be signed to build folder + Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath '$(Pipeline.Workspace)/toBeSigned' -OfficialBuild $officialBuild + + $dlls = Get-ChildItem $BuildPath/*.dll, $BuildPath/*.exe -Recurse + $signatures = $dlls | Get-AuthenticodeSignature + $officialIssuerPattern = '^CN=(Microsoft Code Signing PCA|Microsoft Root Certificate Authority|Microsoft Corporation).*' + $testCert = '^CN=(Microsoft|TestAzureEngBuildCodeSign).*' + $missingSignatures = $signatures | Where-Object { $_.status -eq 'notsigned' -or $_.SignerCertificate.Issuer -notmatch $testCert -or $_.SignerCertificate.Issuer -notmatch $officialIssuerPattern} | select-object -ExpandProperty Path + + Write-Verbose -verbose "to be signed:`r`n $($missingSignatures | Out-String)" + + $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: | + $officialBuild = [System.Convert]::ToBoolean('${{ parameters.OfficialBuild }}') + $vPackScenario = [System.Convert]::ToBoolean('${{ parameters.vPackScenario }}') + Import-Module '$(PowerShellRoot)/build.psm1' -Force + Import-Module '$(PowerShellRoot)/tools/packaging' -Force + $isGlobalTool = '${{ parameters.globalTool }}' -eq 'true' + + if ($vPackScenario) { + Write-Verbose -Verbose -Message "vPackScenario is true, copying to $(ob_outputDirectory)" + $pathForUpload = New-Item -ItemType Directory -Path '$(ob_outputDirectory)' -Force + Write-Verbose -Verbose -Message "pathForUpload: $pathForUpload" + Copy-Item -Path '${{ parameters.binPath }}\*' -Destination $pathForUpload -Recurse -Force -Verbose + Write-Verbose -Verbose -Message "Files copied to $pathForUpload" + } + elseif (-not $isGlobalTool) { + $pathForUpload = New-Item -ItemType Directory -Path '$(ob_outputDirectory)/Signed-$(Runtime)' -Force + Write-Verbose -Verbose -Message "pathForUpload: $pathForUpload" + Copy-Item -Path '${{ parameters.binPath }}\*' -Destination $pathForUpload -Recurse -Force -Verbose + 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 -OfficialBuild $officialBuild + + 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..97d2f4fc46a --- /dev/null +++ b/.pipelines/templates/package-create-msix.yml @@ -0,0 +1,154 @@ +parameters: + - name: OfficialBuild + type: boolean + default: false + +jobs: +- job: CreateMSIXBundle + displayName: Create .msixbundle file + pool: + type: windows + + variables: + - group: msixTools + - group: 'Azure Blob variable group' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: release-SetReleaseTagandContainerName.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows arm64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_x86 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x86 packages + + # Finds the makeappx tool on the machine with image: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - pwsh: | + $cmd = Get-Command makeappx.exe -ErrorAction Ignore + if ($cmd) { + Write-Verbose -Verbose 'makeappx available in PATH' + $exePath = $cmd.Source + } else { + $toolsDir = '$(Pipeline.Workspace)\releasePipeline\tools' + New-Item $toolsDir -Type Directory -Force > $null + $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | + Where-Object { $_.DirectoryName -match 'x64' } | + Select-Object -Last 1 + $exePath = $makeappx.FullName + Write-Verbose -Verbose 'makeappx was found:' + } + $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: Find makeappx tool + retryCountOnTaskFailure: 1 + + - pwsh: | + $sourceDir = '$(Pipeline.Workspace)\releasePipeline\msix' + $null = New-Item -Path $sourceDir -ItemType Directory -Force + + $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*.msix" -Recurse + foreach ($msixFile in $msixFiles) { + $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose + } + + $makeappx = '$(MakeAppxPath)' + $outputDir = "$sourceDir\output" + New-Item $outputDir -Type Directory -Force > $null + + # Separate LTS and Stable/Preview MSIX files by filename convention + $ltsMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -match '-LTS-' }) + $stableMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -notmatch '-LTS-' }) + + Write-Verbose -Verbose "Stable/Preview MSIX files: $($stableMsix.Name -join ', ')" + Write-Verbose -Verbose "LTS MSIX files: $($ltsMsix.Name -join ', ')" + + # Create Stable/Preview bundle + if ($stableMsix.Count -gt 0) { + $stableDir = "$sourceDir\stable" + New-Item $stableDir -Type Directory -Force > $null + $stableMsix | Copy-Item -Destination $stableDir -Force + $file = $stableMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $stableBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating Stable/Preview bundle: $stableBundleName" + & $makeappx bundle /d $stableDir /p "$outputDir\$stableBundleName" + } + + # Create LTS bundle + if ($ltsMsix.Count -gt 0) { + $ltsDir = "$sourceDir\lts" + New-Item $ltsDir -Type Directory -Force > $null + $ltsMsix | Copy-Item -Destination $ltsDir -Force + $file = $ltsMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $ltsBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating LTS bundle: $ltsBundleName" + & $makeappx bundle /d $ltsDir /p "$outputDir\$ltsBundleName" + } + + Write-Verbose -Verbose "Created bundles:" + Get-ChildItem -Path $outputDir -Recurse + + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: Create MsixBundle + retryCountOnTaskFailure: 1 + + - task: onebranch.pipeline.signing@1 + displayName: Sign MsixBundle + condition: eq('${{ parameters.OfficialBuild }}', 'true') + inputs: + command: 'sign' + signing_profile: $(MSIXProfile) + files_to_sign: '**/*.msixbundle' + search_root: '$(BundleDir)' + + - pwsh: | + $signedBundles = @(Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File) + Write-Verbose -Verbose "Signed bundles: $($signedBundles.Name -join ', ')" + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + foreach ($bundle in $signedBundles) { + Copy-Item -Path $bundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + } + + Write-Verbose -Verbose "Uploaded Bundles:" + Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose + displayName: Upload msixbundle to Artifacts diff --git a/.pipelines/templates/package-store-package.yml b/.pipelines/templates/package-store-package.yml new file mode 100644 index 00000000000..6abddae6851 --- /dev/null +++ b/.pipelines/templates/package-store-package.yml @@ -0,0 +1,244 @@ +jobs: +- job: CreateStorePackage + displayName: Create StoreBroker Package + pool: + type: windows + + variables: + - group: 'Azure Blob variable group' + - group: 'Store Publish Variables' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + + steps: + - checkout: self + clean: true + + - template: release-SetReleaseTagandContainerName.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_msixbundle_CreateMSIXBundle + itemPattern: | + **/*.msixbundle + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download signed msixbundle + + - pwsh: | + $bundleDir = '$(Build.ArtifactStagingDirectory)/downloads' + $bundle = Get-ChildItem -Path $bundleDir -Filter '*.msixbundle' -Recurse | Select-Object -First 1 + if (-not $bundle) { + Write-Error "No .msixbundle file found in $bundleDir" + exit 1 + } + Write-Verbose -Verbose "Found bundle: $($bundle.FullName)" + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$($bundle.DirectoryName)" + Write-Host "##$vstsCommandString" + displayName: Locate msixbundle + + - template: channelSelection.yml@self + + - pwsh: | + $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' + $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' + $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" + + # Define app configurations for each channel + $channelConfigs = @{ + 'LTS' = @{ + AppStoreName = 'PowerShell-LTS' + ProductId = '$(productId-LTS)' + AppId = '$(AppID-LTS)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Stable' = @{ + AppStoreName = 'PowerShell' + ProductId = '$(productId-Stable)' + AppId = '$(AppID-Stable)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Preview' = @{ + AppStoreName = 'PowerShell (Preview)' + ProductId = '$(productId-Preview)' + AppId = '$(AppID-Preview)' + ServiceEndpoint = "StoreAppPublish-Preview" + } + } + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + $config = $channelConfigs[$currentChannel] + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" + Write-Verbose -Verbose "Product ID: $($config.ProductId)" + + # Update PDP.xml file + $pdpPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP/en-US/PDP.xml' + if (Test-Path $pdpPath) { + Write-Verbose -Verbose "Updating PDP file: $pdpPath" + + [xml]$pdpXml = Get-Content $pdpPath -Raw + + # Create namespace manager for XML with default namespace + $nsManager = New-Object System.Xml.XmlNamespaceManager($pdpXml.NameTable) + $nsManager.AddNamespace("pd", "http://schemas.microsoft.com/appx/2012/ProductDescription") + + $appStoreNameElement = $pdpXml.SelectSingleNode("//pd:AppStoreName", $nsManager) + if ($appStoreNameElement) { + $appStoreNameElement.SetAttribute("_locID", $config.AppStoreName) + Write-Verbose -Verbose "Updated AppStoreName _locID to: $($config.AppStoreName)" + } else { + Write-Warning "AppStoreName element not found in PDP file" + } + + $pdpXml.Save($pdpPath) + Write-Verbose -Verbose "PDP file updated successfully" + Get-Content -Path $pdpPath | Write-Verbose -Verbose + } else { + Write-Error "PDP file not found: $pdpPath" + exit 1 + } + + # Update SBConfig.json file + $sbConfigPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/SBConfig.json' + if (Test-Path $sbConfigPath) { + Write-Verbose -Verbose "Updating SBConfig file: $sbConfigPath" + + $sbConfigJson = Get-Content $sbConfigPath -Raw | ConvertFrom-Json + + $sbConfigJson.appSubmission.productId = $config.ProductId + Write-Verbose -Verbose "Updated productId to: $($config.ProductId)" + + $sbConfigJson | ConvertTo-Json -Depth 100 | Set-Content $sbConfigPath -Encoding UTF8 + Write-Verbose -Verbose "SBConfig file updated successfully" + Get-Content -Path $sbConfigPath | Write-Verbose -Verbose + } else { + Write-Error "SBConfig file not found: $sbConfigPath" + exit 1 + } + + Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" + Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" + + # Select the correct bundle based on channel + $bundleFiles = @(Get-ChildItem -Path '$(BundleDir)' -Filter '*.msixbundle') + Write-Verbose -Verbose "Available bundles: $($bundleFiles.Name -join ', ')" + + if ($IsLTS) { + $bundleFile = $bundleFiles | Where-Object { $_.Name -match '-LTS-' } + } else { + # Catches Stable or Preview + $bundleFile = $bundleFiles | Where-Object { $_.Name -notmatch '-LTS-' } + } + + if (-not $bundleFile) { + Write-Error "No matching bundle found for channel '$currentChannel'. Available bundles: $($bundleFiles.Name -join ', ')" + exit 1 + } + + # Copy the selected bundle to a dedicated directory for store packaging + $storeBundleDir = '$(Pipeline.Workspace)\releasePipeline\msix\store-bundle' + New-Item $storeBundleDir -Type Directory -Force > $null + Copy-Item -Path $bundleFile.FullName -Destination $storeBundleDir -Force -Verbose + Write-Host "##vso[task.setvariable variable=StoreBundleDir]$storeBundleDir" + Write-Verbose -Verbose "Selected bundle for store packaging: $($bundleFile.Name)" + + # These variables are used in the next tasks to determine which ServiceEndpoint to use + $ltsValue = $IsLTS.ToString().ToLower() + $stableValue = $IsStable.ToString().ToLower() + $previewValue = $IsPreview.ToString().ToLower() + + Write-Verbose -Verbose "About to set variables:" + Write-Verbose -Verbose " LTS=$ltsValue" + Write-Verbose -Verbose " STABLE=$stableValue" + Write-Verbose -Verbose " PREVIEW=$previewValue" + + Write-Host "##vso[task.setvariable variable=LTS]$ltsValue" + Write-Host "##vso[task.setvariable variable=STABLE]$stableValue" + Write-Host "##vso[task.setvariable variable=PREVIEW]$previewValue" + + Write-Verbose -Verbose "Variables set successfully" + name: UpdateConfigs + displayName: Update PDPs and SBConfig.json + + - pwsh: | + Write-Verbose -Verbose "Checking variables after UpdateConfigs:" + Write-Verbose -Verbose "LTS=$(LTS)" + Write-Verbose -Verbose "STABLE=$(STABLE)" + Write-Verbose -Verbose "PREVIEW=$(PREVIEW)" + displayName: Debug - Check Variables + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Preview)' + condition: eq(variables['PREVIEW'], 'true') + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Stable/LTS)' + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) + inputs: + serviceEndpoint: 'StoreAppPublish-Stable' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + Get-Item -Path "$(System.DefaultWorkingDirectory)/SBLog.txt" -ErrorAction SilentlyContinue | + Copy-Item -Destination $outputDirectory -Verbose + displayName: Upload Store Failure Log + condition: failed() + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" + $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" + $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" + + if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { + Write-Verbose -Verbose "Uploading StoreBroker Package files:" + Write-Verbose -Verbose "JSON File: $jsonFile" + Write-Verbose -Verbose "ZIP File: $zipFile" + + Copy-Item -Path $submissionPackageDir -Destination $outputDirectory -Verbose -Recurse + } + else { + Write-Error "Required files not found in $submissionPackageDir" + exit 1 + } + displayName: 'Upload StoreBroker Package' diff --git a/.pipelines/templates/packaging/windows/package.yml b/.pipelines/templates/packaging/windows/package.yml new file mode 100644 index 00000000000..8b0e230b6b8 --- /dev/null +++ b/.pipelines/templates/packaging/windows/package.yml @@ -0,0 +1,225 @@ +parameters: + runtime: x64 + +jobs: +- job: build_win_${{ parameters.runtime }} + displayName: Build Windows Packages ${{ parameters.runtime }} + condition: succeeded() + pool: + type: windows + + variables: + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage, signing happens in separate stage + - name: ob_artifactBaseName + value: drop_windows_package_${{ parameters.runtime }} + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + 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: false # Disable for build-only, enable in signing stage + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: Runtime + value: ${{ parameters.runtime }} + - group: msixTools + + steps: + - checkout: self + clean: true + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + ob_restore_phase: false + + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + ob_restore_phase: false + + - template: /.pipelines/templates/rebuild-branch-check.yml@self + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_${{ parameters.runtime }}_release + displayName: Download signed artifacts + condition: ${{ ne(parameters.runtime, 'minSize') }} + + - download: CoOrdinatedBuildPipeline + artifact: drop_windows_build_windows_x64_${{ parameters.runtime }} + displayName: Download minsize signed artifacts + condition: ${{ eq(parameters.runtime, 'minSize') }} + + - 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 + + - template: /.pipelines/templates/install-dotnet.yml@self + parameters: + ob_restore_phase: false + + - 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 Both + + Find-Dotnet + + $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 + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + $Stable = [bool]$metadata.StableRelease.Package + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" + Write-Verbose -Verbose "Stable: $Stable" + + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" + } + + Start-PSBootstrap -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' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } + 'fxdependent' { 'fxdependent' } + 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } + 'minsize' { 'min-size' } + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + Set-Location $repoRoot + + Start-PSPackage -Type $packageTypes -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS + + # When both LTS and Stable are requested, also build the Stable MSIX + if ($packageTypes -contains 'msix' -and $LTS -and $Stable) { + Write-Verbose -Verbose "Both LTS and Stable packages requested. Building additional Stable MSIX." + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + } + + displayName: 'Build Packages (Unsigned)' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + + # Copy unsigned packages to output directory + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $packageTypes = switch ($runtime) { + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } + 'fxdependent' { 'fxdependent' } + 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } + 'minsize' { 'min-size' } + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { + $zipPkgNameFilter = "powershell-*.zip" + $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "unsigned 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 "unsigned msixPkgPath: $msixPkgPath" + Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + displayName: Copy unsigned packages to output directory + + - pwsh: | + Get-ChildItem -Path $(ob_outputDirectory) -Recurse + displayName: 'List unsigned artifacts' diff --git a/.pipelines/templates/packaging/windows/sign.yml b/.pipelines/templates/packaging/windows/sign.yml new file mode 100644 index 00000000000..151b5d676fb --- /dev/null +++ b/.pipelines/templates/packaging/windows/sign.yml @@ -0,0 +1,117 @@ +parameters: + runtime: x64 + +jobs: +- job: sign_win_${{ parameters.runtime }} + displayName: Sign Windows Packages ${{ parameters.runtime }} + condition: succeeded() + pool: + type: windows + + variables: + - name: runCodesignValidationInjection + value: false + - name: ob_artifactBaseName + value: drop_windows_package_package_win_${{ parameters.runtime }} + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)\ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: Runtime + value: ${{ parameters.runtime }} + - group: msixTools + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + + - template: /.pipelines/templates/shouldSign.yml@self + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + # Download unsigned packages from the build stage + - download: current + artifact: drop_windows_package_${{ parameters.runtime }} + displayName: Download unsigned packages + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Downloaded unsigned artifacts:" + Get-ChildItem "$(Pipeline.Workspace)\drop_windows_package_${{ parameters.runtime }}" -Recurse + displayName: 'Capture Downloaded Unsigned Artifacts' + continueOnError: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/install-dotnet.yml@self + + # Import build.psm1 and bootstrap packaging dependencies + - pwsh: | + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + Write-Verbose -Verbose "Modules imported successfully" + displayName: 'Import modules' + env: + ob_restore_phase: true + + # Copy all signed packages to output directory + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $packageTypes = switch ($runtime) { + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } + 'fxdependent' { 'fxdependent' } + 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } + 'minsize' { 'min-size' } + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { + $zipPkgNameFilter = "powershell-*.zip" + $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed zipPkgPath: $zipPkgPath" + Copy-Item -Path $zipPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + + if ($packageTypes -contains 'msix') { + $msixPkgNameFilter = "PowerShell*.msix" + $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed msixPkgPath: $msixPkgPath" + Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + displayName: Copy signed packages to output directory + + - pwsh: | + Get-ChildItem -Path $(ob_outputDirectory) -Recurse + displayName: 'List signed artifacts' + env: + ob_restore_phase: true diff --git a/.pipelines/templates/rebuild-branch-check.yml b/.pipelines/templates/rebuild-branch-check.yml new file mode 100644 index 00000000000..a4b546a0dc6 --- /dev/null +++ b/.pipelines/templates/rebuild-branch-check.yml @@ -0,0 +1,17 @@ +# This template checks if the current branch is a rebuild branch +# and sets an output variable IsRebuildBranch that can be used by other templates +steps: +- pwsh: | + # Check if this is a rebuild branch (e.g., rebuild/v7.4.13-rebuild.5) + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + + $value = if ($isRebuildBranch) { 'true' } else { 'false' } + Write-Verbose -Message "IsRebuildBranch: $value" -Verbose + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected: $(Build.SourceBranch)" -Verbose + } + + Write-Host "##vso[task.setvariable variable=IsRebuildBranch;isOutput=true]$value" + name: RebuildBranchCheck + displayName: Check if Rebuild Branch diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml new file mode 100644 index 00000000000..cbbdb70cc4f --- /dev/null +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -0,0 +1,138 @@ +parameters: + - name: skipMSIXPublish + type: boolean + +jobs: +- job: Store_Publish_MSIX + displayName: Publish MSIX to the Microsoft Store + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_store_package_CreateStorePackage + variables: + - group: 'Store Publish Variables' + - name: LTS + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsLTS'] ] + - name: STABLE + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsStable'] ] + - name: PREVIEW + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsPreview'] ] + - template: ./variables/release-shared.yml@self + parameters: + RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Release Tag: $(ReleaseTag)" + Get-ChildItem $(Pipeline.Workspace) -Recurse | Select-Object -ExpandProperty FullName + displayName: 'Capture ReleaseTag and Downloaded Packages' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + if ("$(ReleaseTag)" -eq '') { + Write-Error "ReleaseTag is not set. Cannot proceed with publishing to the Store." + exit 1 + } + $middleURL = '' + $tagString = "$(ReleaseTag)" + if ($tagString -match '-preview') { + $middleURL = "preview" + } + elseif ($tagString -match '(\d+\.\d+)') { + $middleURL = $matches[1] + } + + $endURL = $tagString -replace '^v','' -replace '\.','' + $message = "Changelog: https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" + Write-Verbose -Verbose "Release Notes for the Store:" + Write-Verbose -Verbose "$message" + $jsonPath = "$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json" + $json = Get-Content $jsonPath -Raw | ConvertFrom-Json + + $json.listings.'en-us'.baseListing.releaseNotes = $message + + # Add PowerShell version to the top of the description + $description = $json.listings.'en-us'.baseListing.description + $version = "$(ReleaseTag)" + $updatedDescription = "Version: $version`n`n$description" + $json.listings.'en-us'.baseListing.description = $updatedDescription + Write-Verbose -Verbose "Updated description: $updatedDescription" + + $json | ConvertTo-Json -Depth 100 | Set-Content $jsonPath -Encoding UTF8 + displayName: 'Add Changelog Link and Version Number to SBJSON' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + # Convert ADO variables to PowerShell boolean variables + $IsLTS = '$(LTS)' -eq 'true' + $IsStable = '$(STABLE)' -eq 'true' + $IsPreview = '$(PREVIEW)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $(LTS), Stable: $(STABLE), Preview: $(PREVIEW)" + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + # Assign AppID for Store-Publish Task + $appID = $null + if ($IsLTS) { + $appID = '$(AppID-LTS)' + } + elseif ($IsStable) { + $appID = '$(AppID-Stable)' + } + else { + $appID = '$(AppID-Preview)' + } + + Write-Host "##vso[task.setvariable variable=AppID]$appID" + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "Conditional tasks will handle the publishing based on channel variables" + displayName: 'Validate Channel Selection' + + - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 + displayName: 'Publish StoreBroker Package (Stable/LTS)' + condition: and(not(${{ parameters.skipMSIXPublish }}), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) + inputs: + serviceEndpoint: 'StoreAppPublish-Stable' + appId: '$(AppID)' + inputMethod: JsonAndZip + jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' + zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true + numberOfPackagesToKeep: 2 + jsonZipUpdateMetadata: true + targetPublishMode: 'Immediate' + skipPolling: true + + - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 + displayName: 'Publish StoreBroker Package (Preview)' + condition: and(not(${{ parameters.skipMSIXPublish }}), eq(variables['PREVIEW'], 'true')) + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + appId: '$(AppID)' + inputMethod: JsonAndZip + jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' + zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true + numberOfPackagesToKeep: 2 + jsonZipUpdateMetadata: true + targetPublishMode: 'Immediate' + skipPolling: true diff --git a/.pipelines/templates/release-MakeBlobPublic.yml b/.pipelines/templates/release-MakeBlobPublic.yml new file mode 100644 index 00000000000..758298202a1 --- /dev/null +++ b/.pipelines/templates/release-MakeBlobPublic.yml @@ -0,0 +1,177 @@ +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: 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: 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..b33e652b3c7 --- /dev/null +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -0,0 +1,51 @@ +jobs: +- job: setTagAndChangelog + displayName: Set Tag and Upload Changelog + condition: succeeded() + pool: + type: windows + variables: + - group: 'mscodehub-code-read-akv' + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + steps: + - template: release-SetReleaseTagandContainerName.yml@self + + - checkout: self + clean: true + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Release Tag: $(OutputReleaseTag.releaseTag)" + $releaseVersion = '$(OutputReleaseTag.releaseTag)' -replace '^v','' + Write-Verbose -Verbose "Release Version: $releaseVersion" + $semanticVersion = [System.Management.Automation.SemanticVersion]$releaseVersion + + $isPreview = $semanticVersion.PreReleaseLabel -ne $null + + $fileName = if ($isPreview) { + "preview.md" + } + else { + $semanticVersion.Major.ToString() + "." + $semanticVersion.Minor.ToString() + ".md" + } + + $filePath = "$(Build.SourcesDirectory)/PowerShell/CHANGELOG/$fileName" + Write-Verbose -Verbose "Selected Log file: $filePath" + + if (-not (Test-Path -Path $filePath)) { + Write-Error "Changelog file not found: $filePath" + exit 1 + } + + Write-Verbose -Verbose "Creating output directory for CHANGELOG: $(ob_outputDirectory)/CHANGELOG" + New-Item -Path $(ob_outputDirectory)/CHANGELOG -ItemType Directory -Force + Copy-Item -Path $filePath -Destination $(ob_outputDirectory)/CHANGELOG + displayName: Upload Changelog + + - template: channelSelection.yml@self diff --git a/.pipelines/templates/release-githubNuget.yml b/.pipelines/templates/release-githubNuget.yml new file mode 100644 index 00000000000..27a358c7e75 --- /dev/null +++ b/.pipelines/templates/release-githubNuget.yml @@ -0,0 +1,228 @@ +parameters: + - name: skipPublish + type: boolean + +jobs: +- job: GithubReleaseDraft + displayName: Create GitHub Release Draft + condition: succeeded() + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_setReleaseTagAndChangelog_SetTagAndChangelog + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_upload_upload_packages + variables: + - template: ./variables/release-shared.yml@self + parameters: + RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] + + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Release Tag: $(ReleaseTag)" + Get-ChildItem Env: | Out-String -Stream | Write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + $Path = "$(Pipeline.Workspace)/GitHubPackages" + + # The .exe packages are for Windows Update only and should not be uploaded to GitHub release. + $exefiles = Get-ChildItem -Path $Path -Filter *.exe + if ($exefiles) { + Write-Verbose -Verbose "Remove .exe packages:" + $exefiles | Remove-Item -Force -Verbose + } + + # The .msi packages should not be uploaded to GitHub release. + $msifiles = Get-ChildItem -Path $Path -Filter *.msi + if ($msifiles) { + Write-Verbose -Verbose "Remove .msi packages:" + $msifiles | Remove-Item -Force -Verbose + } + + $OutputPath = Join-Path $Path 'hashes.sha256' + $packages = Get-ChildItem -Path $Path -Include * -Recurse -File + $checksums = $packages | + ForEach-Object { + Write-Verbose -Verbose "Generating checksum file for $($_.FullName)" + $packageName = $_.Name + $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash.ToLower() + # the '*' before the packagename signifies it is a binary + "$hash *$packageName" + } + $checksums | Out-File -FilePath $OutputPath -Force + $fileContent = Get-Content -Path $OutputPath -Raw | Out-String + Write-Verbose -Verbose -Message $fileContent + displayName: Add sha256 hashes + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Get-ChildItem $(Pipeline.Workspace) -recurse | Select-Object -ExpandProperty FullName + displayName: List all files in the workspace + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + $releaseVersion = '$(ReleaseTag)' -replace '^v','' + Write-Verbose -Verbose "Available modules: " + Get-Module | Write-Verbose -Verbose + + $filePath = Get-ChildItem -Path "$(Pipeline.Workspace)/CHANGELOG" -Filter '*.md' | Select-Object -First 1 -ExpandProperty FullName + + if (-not (Test-Path $filePath)) { + throw "$filePath not found" + } + + $changelog = Get-Content -Path $filePath + + $headingPattern = "^## \[\d+\.\d+\.\d+" + $headingStartLines = @($changelog | Select-String -Pattern $headingPattern | Select-Object -ExpandProperty LineNumber) + + if ($headingStartLines.Count -eq 0) { + throw "No release heading matching '$headingPattern' found in $filePath" + } + + $startLine = $headingStartLines[0] + if ($headingStartLines.Count -ge 2) { + $endLine = $headingStartLines[1] - 1 + } else { + # Only one release heading present; take through end of file. + $endLine = $changelog.Count + } + + $clContent = $changelog | Select-Object -Skip ($startLine-1) -First ($endLine - $startLine + 1) | Out-String + + $StringBuilder = [System.Text.StringBuilder]::new($clContent, $clContent.Length + 2kb) + $StringBuilder.AppendLine().AppendLine() > $null + $StringBuilder.AppendLine("### SHA256 Hashes of the release artifacts").AppendLine() > $null + Get-ChildItem -Path "$(Pipeline.Workspace)/GitHubPackages/" -File | ForEach-Object { + $PackageName = $_.Name + $SHA256 = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash + $StringBuilder.AppendLine("- $PackageName").AppendLine(" - $SHA256") > $null + } + + $clContent = $StringBuilder.ToString() + + Write-Verbose -Verbose "Selected content: `n$clContent" + + $releaseNotesFilePath = "$(Pipeline.Workspace)/release-notes.md" + $clContent | Out-File -FilePath $releaseNotesFilePath -Encoding utf8 + + Write-Host "##vso[task.setvariable variable=ReleaseNotesFilePath;]$releaseNotesFilePath" + + #if name has prelease then make prerelease true as a variable + if ($releaseVersion -like '*-*') { + Write-Host "##vso[task.setvariable variable=IsPreRelease;]true" + } else { + Write-Host "##vso[task.setvariable variable=IsPreRelease;]false" + } + displayName: Set variables for GitHub release task + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Host "ReleaseNotes content:" + Get-Content "$(Pipeline.Workspace)/release-notes.md" -Raw | Out-String -width 9999 | Write-Host + displayName: Verify Release Notes + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + $middleURL = '' + $tagString = "$(ReleaseTag)" + Write-Verbose -Verbose "Use the following command to push the tag:" + if ($tagString -match '-preview') { + $middleURL = "preview" + } + elseif ($tagString -match '(\d+\.\d+)') { + $middleURL = $matches[1] + } + $endURL = $tagString -replace '^v|\.', '' + $message = "https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" + Write-Verbose -Verbose "git tag -a $(ReleaseTag) $env:BUILD_SOURCEVERSION -m $message" + displayName: Git Push Tag Command + + - task: GitHubRelease@1 + inputs: + gitHubConnection: GitHubReleasePAT + repositoryName: PowerShell/PowerShell + target: master + assets: '$(Pipeline.Workspace)/GitHubPackages/*' + tagSource: 'userSpecifiedTag' + tag: '$(ReleaseTag)' + title: "$(ReleaseTag) Release of PowerShell" + isDraft: true + addChangeLog: false + action: 'create' + releaseNotesFilePath: '$(ReleaseNotesFilePath)' + isPrerelease: '$(IsPreRelease)' + +- job: NuGetPublish + displayName: Publish to NuGet + condition: succeeded() + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_upload_upload_packages + variables: + - template: ./variables/release-shared.yml@self + parameters: + VERSION: $[ stageDependencies.setReleaseTagAndChangelog.SetTagAndChangelog.outputs['OutputVersion.Version'] ] + + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Version: $(Version)" + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + #Exclude all global tool packages. Their names start with 'PowerShell.' + $null = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/release" + Copy-Item "$(Pipeline.Workspace)/NuGetPackages/*.nupkg" -Destination "$(Pipeline.Workspace)/release" -Exclude "PowerShell.*.nupkg" -Force -Verbose + + $releaseVersion = '$(Version)' + $globalToolPath = "$(Pipeline.Workspace)/NuGetPackages/PowerShell.$releaseVersion.nupkg" + + if ($releaseVersion -notlike '*-*') { + # Copy the global tool package for stable releases + Copy-Item $globalToolPath -Destination "$(Pipeline.Workspace)/release" + } + + Write-Verbose -Verbose "The .nupkgs below will be pushed:" + Get-ChildItem "$(Pipeline.Workspace)/release" -recurse + displayName: Download and capture nupkgs + condition: and(ne('${{ parameters.skipPublish }}', 'true'), succeeded()) + + - task: NuGetCommand@2 + displayName: 'NuGet push' + condition: and(ne('${{ parameters.skipPublish }}', 'true'), succeeded()) + inputs: + command: push + packagesToPush: '$(Pipeline.Workspace)/release/*.nupkg' + nuGetFeedType: external + publishFeedCredentials: PowerShellNuGetOrgPush diff --git a/.pipelines/templates/release-prep-for-ev2.yml b/.pipelines/templates/release-prep-for-ev2.yml new file mode 100644 index 00000000000..3ad716a3af4 --- /dev/null +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -0,0 +1,231 @@ +parameters: +- name: skipPublish + type: boolean + default: false + +stages: +- stage: PrepForEV2 + displayName: 'Copy and prep all files needed for EV2 stage' + jobs: + - job: CopyEV2FilesToArtifact + displayName: 'Copy EV2 Files to Artifact' + pool: + type: linux + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_deb + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_rpm + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_mariner_x64 + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_mariner_arm64 + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: repoRoot + value: '$(Build.SourcesDirectory)/PowerShell' + - name: ev2ServiceGroupRootFolder + value: '$(Build.SourcesDirectory)/PowerShell/.pipelines/EV2Specs/ServiceGroupRoot' + - name: ev2ParametersFolder + value: '$(Build.SourcesDirectory)/PowerShell/.pipelines/EV2Specs/ServiceGroupRoot/Parameters' + - group: 'mscodehub-code-read-akv' + - group: 'packages.microsoft.com' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)/PowerShell/.config/tsaoptions.json + steps: + - checkout: self ## the global setting on lfs didn't work + lfs: false + env: + ob_restore_phase: true + + - template: release-SetReleaseTagandContainerName.yml + parameters: + restorePhase: true + + - pwsh: | + $packageVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '^v','' + $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: Set Package version + env: + ob_restore_phase: true + + - pwsh: | + $branch = 'mirror-target' + $gitArgs = "clone", + "--verbose", + "--branch", + "$branch", + "https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools", + '$(Pipeline.Workspace)/tools' + $gitArgs | Write-Verbose -Verbose + git $gitArgs + displayName: Clone Internal-PowerShellTeam-Tools from MSCodeHub + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem Env: | Out-String -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem '$(Build.SourcesDirectory)' + displayName: 'Capture BuildDirectory' + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem '$(Pipeline.Workspace)' -Recurse | Out-String -Stream | write-Verbose -Verbose + displayName: 'Capture Workspace' + env: + ob_restore_phase: true + + - pwsh: | + New-Item -Path '$(ev2ParametersFolder)' -ItemType Directory + displayName: 'Create Parameters folder under EV2Specs folder' + env: + ob_restore_phase: true + + - task: PipAuthenticate@1 + inputs: + artifactFeeds: 'PowerShellCore/PowerShellCore_PublicPackages' + displayName: 'Pip Authenticate' + env: + ob_restore_phase: true + + - pwsh: | + python3 -m pip install --upgrade pip + pip --version --verbose + + Write-Verbose -Verbose "Download pmc-cli to folder without installing it" + $pythonDlFolderPath = Join-Path '$(ev2ServiceGroupRootFolder)/Shell/Run' -ChildPath "python_dl" + pip download -d $pythonDlFolderPath pmc-cli --platform=manylinux_2_17_x86_64 --only-binary=:all: --verbose + displayName: 'Download pmc-cli package' + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Copy ESRP signed .deb and .rpm packages" + # templateContext.inputs places the PSPackagesOfficial pipelineArtifact files + # directly under $(Pipeline.Workspace), not in per-artifact subfolders. + $downloadedPipelineFolder = '$(Pipeline.Workspace)' + $srcFilesFolder = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'SourceFiles' + New-Item -Path $srcFilesFolder -ItemType Directory + $packagesFolder = Join-Path -Path $srcFilesFolder -ChildPath 'packages' + New-Item -Path $packagesFolder -ItemType Directory + + $packageFiles = Get-ChildItem -Path $downloadedPipelineFolder -File | Where-Object { $_.Extension -in '.deb', '.rpm' } + foreach ($file in $packageFiles) + { + Write-Verbose -Verbose "copying file: $($file.FullName)" + Copy-Item -Path $($file.FullName) -Destination $packagesFolder -Verbose + } + + $packagesTarGzDestination = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath 'packages.tar.gz' + tar -czvf $packagesTarGzDestination -C $packagesFolder . + displayName: 'Copy signed .deb and .rpm packages to .tar.gz to pass as a file var to shell extension' + env: + ob_restore_phase: true + + - pwsh: | + $pathToPMCMetadataFile = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath 'pmcMetadata.json' + + $metadata = Get-Content -Path "$(repoRoot)/tools/metadata.json" -Raw | ConvertFrom-Json + $metadataHash = @{} + $skipPublishValue = '${{ parameters.skipPublish }}' + $metadataHash["ReleaseTag"] = '$(OutputReleaseTag.ReleaseTag)' + $metadataHash["LTS"] = $metadata.LTSRelease.PublishToChannels + $metadataHash["ForProduction"] = $true + $metadataHash["SkipPublish"] = [System.Convert]::ToBoolean($skipPublishValue) + + $metadataHash | ConvertTo-Json | Out-File $pathToPMCMetadataFile + + $mappingFilePath = Join-Path -Path '$(repoRoot)/tools/packages.microsoft.com' -ChildPath 'mapping.json' + $mappingFilePathExists = Test-Path $mappingFilePath + $mappingFileEV2Path = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath "mapping.json" + Write-Verbose -Verbose "Copy mapping.json file at: $mappingFilePath which exists: $mappingFilePathExists to: $mappingFileEV2Path" + Copy-Item -Path $mappingFilePath -Destination $mappingFileEV2Path + displayName: 'Create pmcScriptMetadata.json and mapping.json file' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'RolloutSpec.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + $content.RolloutMetadata.Notification.Email.To = '$(PmcEV2SupportEmail)' + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 4 | Out-File $pathToJsonFile + displayName: 'Replace values in RolloutSpecPath.json' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'UploadLinux.Rollout.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + + $identityString = "/subscriptions/$(PmcSubscription)/resourcegroups/$(PmcResourceGroup)/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$(PmcMIName)" + $content.shellExtensions.launch.identity.userAssignedIdentities[0] = $identityString + + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 6 | Out-File $pathToJsonFile + displayName: 'Replace values in UploadLinux.Rollout.json file' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'ServiceModel.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + $content.ServiceResourceGroups[0].AzureResourceGroupName = '$(PmcResourceGroup)' + $content.ServiceResourceGroups[0].AzureSubscriptionId = '$(PmcSubscription)' + + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 9 | Out-File $pathToJsonFile + displayName: 'Replace values in ServiceModel.json' + env: + ob_restore_phase: true + + - pwsh: | + $settingFilePath = Join-Path '$(ev2ServiceGroupRootFolder)/Shell/Run' -ChildPath 'settings.toml' + New-Item -Path $settingFilePath -ItemType File + $pmcMIClientID = '$(PmcMIClientID)' + $pmcEndpoint = '$(PmcEndpointUrl)' + + Add-Content -Path $settingFilePath -Value "[default]" + Add-Content -Path $settingFilePath -Value "base_url = `"$pmcEndpoint`"" + Add-Content -Path $settingFilePath -Value "auth_type = `"msi`"" + Add-Content -Path $settingFilePath -Value "client_id = `"$pmcMIClientID`"" + displayName: 'Create settings.toml file with MI clientId populated' + env: + ob_restore_phase: true + + - task: onebranch.pipeline.signing@1 + inputs: + command: 'sign' + signing_profile: external_distribution + files_to_sign: '*.ps1' + search_root: '$(repoRoot)/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run' + displayName: Sign Run.ps1 + + - pwsh: | + # folder to tar must have: Run.ps1, settings.toml, python_dl + $srcPath = Join-Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'Shell' + $pathToRunTarFile = Join-Path $srcPath -ChildPath "Run.tar" + tar -cvf $pathToRunTarFile -C $srcPath ./Run + displayName: 'Create archive for the shell extension' + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(repoRoot)/.pipelines' + Contents: 'EV2Specs/**' + TargetFolder: $(ob_outputDirectory) diff --git a/.pipelines/templates/release-publish-pmc.yml b/.pipelines/templates/release-publish-pmc.yml new file mode 100644 index 00000000000..dc7fc8534e3 --- /dev/null +++ b/.pipelines/templates/release-publish-pmc.yml @@ -0,0 +1,56 @@ +parameters: +- name: releaseEnvironment + type: string + default: Production + values: + - Production + - PPE + - Test +- name: approvalServiceEnvironment + type: string + default: Production + values: + - Production + - PPE + - Test +# OneBranch requires the stage name to be prefixed with the release environment. +# Official uses 'Prod' for Production; NonProd validators require '' (e.g. 'Test', 'PPE'). +- name: stagePrefix + type: string + default: Prod +# When true, the Ev2 push step is skipped. Useful for NonOfficial dry-runs that +# only want to validate artifact download via templateContext.inputs. +- name: skipEv2Push + type: boolean + default: false + +stages: +- stage: ${{ parameters.stagePrefix }}_Release + displayName: 'Deploy packages to PMC with EV2' + dependsOn: + - PrepForEV2 + variables: + - name: ob_release_environment + value: ${{ parameters.releaseEnvironment }} + - name: repoRoot + value: $(Build.SourcesDirectory) + jobs: + - job: ${{ parameters.stagePrefix }}_ReleaseJob + displayName: Publish to PMC + pool: + type: release + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_PrepForEV2_CopyEv2FilesToArtifact + + steps: + - ${{ if not(parameters.skipEv2Push) }}: + - task: vsrm-ev2.vss-services-ev2.adm-release-task.ExpressV2Internal@1 + displayName: 'Ev2: Push to PMC' + inputs: + UseServerMonitorTask: true + EndpointProviderType: ApprovalService + ApprovalServiceEnvironment: ${{ parameters.approvalServiceEnvironment }} + ServiceRootPath: '$(Pipeline.Workspace)/EV2Specs/ServiceGroupRoot' + RolloutSpecPath: '$(Pipeline.Workspace)/EV2Specs/ServiceGroupRoot/RolloutSpec.json' diff --git a/.pipelines/templates/release-symbols.yml b/.pipelines/templates/release-symbols.yml new file mode 100644 index 00000000000..a628f4d7127 --- /dev/null +++ b/.pipelines/templates/release-symbols.yml @@ -0,0 +1,89 @@ +parameters: + - name: skipPublish + default: false + type: boolean + +jobs: +- job: PublishSymbols + displayName: Publish Symbols + condition: succeeded() + pool: + type: windows + variables: + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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..9e3d6a6accb --- /dev/null +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -0,0 +1,164 @@ +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: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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 + # The build itself has already determined if it is preview or stable/LTS, + # we just need to check via the file name + $isPreview = $fileName -eq "preview.json" + $isStable = $fileName -eq "stable.json" + + $dateTime = [datetime]::UtcNow + $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) + + $metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json + # Note: version tags in metadata.json (e.g. StableReleaseTag) may not reflect the current release being + # published, so they must not be used to gate channel decisions. Use the explicit publish flags instead. + $stableRelease = $metadata.StableRelease.PublishToChannels + $ltsRelease = $metadata.LTSRelease.PublishToChannels + + Write-Verbose -Verbose "Writing $jsonFile contents:" + $buildInfoJsonContent = Get-Content $jsonFile -Encoding UTF8NoBom -Raw + Write-Verbose -Verbose $buildInfoJsonContent + + $buildInfo = $buildInfoJsonContent | ConvertFrom-Json + $buildInfo.ReleaseDate = $dateTime + $currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v','' + + $targetFile = "$ENV:PIPELINE_WORKSPACE/$fileName" + ConvertTo-Json -InputObject $buildInfo | Out-File $targetFile -Encoding ascii + + if ($isPreview) { + Set-BuildVariable -Name UploadPreview -Value YES + } else { + Set-BuildVariable -Name UploadPreview -Value NO + } + + Set-BuildVariable -Name PreviewBuildInfoFile -Value $targetFile + + ## Create 'lts.json' if marked as a LTS release. + if ($isStable) { + if ($ltsRelease) { + $ltsFile = "$ENV:PIPELINE_WORKSPACE/lts.json" + Copy-Item -Path $targetFile -Destination $ltsFile -Force + Set-BuildVariable -Name LTSBuildInfoFile -Value $ltsFile + Set-BuildVariable -Name UploadLTS -Value YES + } else { + Set-BuildVariable -Name UploadLTS -Value NO + } + + ## Gate stable.json upload on the metadata publish flag. + if ($stableRelease) { + Set-BuildVariable -Name StableBuildInfoFile -Value $targetFile + Set-BuildVariable -Name UploadStable -Value YES + } else { + Set-BuildVariable -Name UploadStable -Value NO + } + + ## Always publish the version-specific {Major}-{Minor}.json for non-preview builds. + [System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag + $versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json" + Copy-Item -Path $targetFile -Destination $versionFile -Force + Set-BuildVariable -Name VersionSpecificBuildInfoFile -Value $versionFile + Set-BuildVariable -Name UploadVersionSpecific -Value YES + + } else { + Set-BuildVariable -Name UploadStable -Value NO + Set-BuildVariable -Name UploadVersionSpecific -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 + + #preview + if ($env:UploadPreview -eq 'YES') { + $jsonFile = "$env:PreviewBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + + #LTS + if ($env:UploadLTS -eq 'YES') { + $jsonFile = "$env:LTSBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + + #stable + if ($env:UploadStable -eq 'YES') { + $jsonFile = "$env:StableBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + + #version-specific + if ($env:UploadVersionSpecific -eq 'YES') { + $jsonFile = "$env:VersionSpecificBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES'), eq(variables['UploadVersionSpecific'], 'YES'))) diff --git a/.pipelines/templates/release-validate-fxdpackages.yml b/.pipelines/templates/release-validate-fxdpackages.yml new file mode 100644 index 00000000000..3f4f9a3bb6c --- /dev/null +++ b/.pipelines/templates/release-validate-fxdpackages.yml @@ -0,0 +1,118 @@ +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' + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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_NOLOGO=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..8c2031d5cc9 --- /dev/null +++ b/.pipelines/templates/release-validate-globaltools.yml @@ -0,0 +1,127 @@ +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' + + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + + $toolPath = New-Item -ItemType Directory "$(System.DefaultWorkingDirectory)/toolPath" | Select-Object -ExpandProperty FullName + + Write-Verbose -Verbose "dotnet tool list -g" + 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" + + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + + $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..5953366ffd7 --- /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\-(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 | ForEach-Object { + if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(zip){1}') + { + $messageInstance = "$($_.Name) is not a valid package name" + $message += $messageInstance + Write-Warning $messageInstance + } + } + + if($message.count -gt 0){throw ($message | out-string)} + displayName: Validate Zip Package Names + + - pwsh: | + $message = @() + Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.deb | ForEach-Object { + if($_.Name -notmatch 'powershell(-preview|-lts)?_\d+\.\d+\.\d+([\-~][a-z]*.\d+)?-\d\.deb_(amd64|arm64)\.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..3b0442f65d6 --- /dev/null +++ b/.pipelines/templates/release-validate-sdk.yml @@ -0,0 +1,98 @@ +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' + + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + $repoRoot = "$(Build.SourcesDirectory)" + + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + + $env:DOTNET_NOLOGO=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/set-reporoot.yml b/.pipelines/templates/set-reporoot.yml new file mode 100644 index 00000000000..af7983afaa1 --- /dev/null +++ b/.pipelines/templates/set-reporoot.yml @@ -0,0 +1,35 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: +- pwsh: | + $path = "./build.psm1" + if($env:REPOROOT){ + Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose + exit 0 + } + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ." -Verbose + $repoRoot = '.' + } + else{ + $path = "./PowerShell/build.psm1" + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ./PowerShell" -Verbose + $repoRoot = './PowerShell' + } + } + if($repoRoot) { + $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + } else { + Write-Verbose -Verbose "repo not found" + } + displayName: 'Set repo Root' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/shouldSign.yml b/.pipelines/templates/shouldSign.yml new file mode 100644 index 00000000000..f3701acbc97 --- /dev/null +++ b/.pipelines/templates/shouldSign.yml @@ -0,0 +1,30 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: +- powershell: | + $shouldSign = $true + $authenticodeCert = '$(authenticode_cert_id)' + $msixCert = '$(authenticode_cert_id)' + if($env:IS_DAILY -eq 'true') + { + $authenticodeCert = '$(authenticode_test_cert_id)' + } + 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: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml new file mode 100644 index 00000000000..cd0a4ebc065 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml @@ -0,0 +1,202 @@ +parameters: + - name: RUN_WINDOWS + type: boolean + default: true + - name: RUN_TEST_AND_RELEASE + type: boolean + default: true + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + jobs: + - job: SetVars + displayName: Set Variables + pool: + type: linux + + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_codeSignValidation_enabled + value: false + - name: ob_sdl_codeql_compiled_enabled + value: false + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_sbom_enabled + value: false + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment variables + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + +- stage: macos + displayName: macOS - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: x64 + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: arm64 + +- stage: linux + displayName: linux - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64_minSize' + BuildConfiguration: 'minSize' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm' + JobName: 'linux_arm' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm64' + JobName: 'linux_arm64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-x64' + JobName: 'linux_fxd_x64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-arm64' + JobName: 'linux_fxd_arm64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-noopt-linux-musl-x64' + JobName: 'linux_fxd_x64_alpine' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent' + JobName: 'linux_fxd' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-musl-x64' + JobName: 'linux_x64_alpine' + +- stage: windows + displayName: windows - build and sign + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: release + JobName: build_windows_x64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: minSize + JobName: build_windows_x64_minSize_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x86 + JobName: build_windows_x86_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: arm64 + JobName: build_windows_arm64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependent + JobName: build_windows_fxdependent_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependentWinDesktop + JobName: build_windows_fxdependentWinDesktop_release + +- stage: test_and_release_artifacts + displayName: Test and Release Artifacts + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) + jobs: + - template: /.pipelines/templates/testartifacts.yml@self + + - job: release_json + displayName: Create and Upload release.json + pool: + type: windows + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + steps: + - checkout: self + clean: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/rebuild-branch-check.yml@self + - powershell: | + $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't mark as LTS release for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, not marking as LTS release" -Verbose + } + + @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" + Get-Content "$(Build.StagingDirectory)\release.json" + + if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { + New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" + } + + Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force + displayName: Create and upload release.json file to build artifact + retryCountOnTaskFailure: 2 + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml new file mode 100644 index 00000000000..399d242aa4b --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml @@ -0,0 +1,197 @@ +parameters: + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + displayName: 'Prep BuildInfo+Az' + jobs: + - template: /.pipelines/templates/checkAzureContainer.yml@self + +- stage: mac_package + displayName: 'macOS Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: x64 + + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: arm64 + +- stage: windows_package_build + displayName: 'Win Pkg (unsigned)' + dependsOn: [] + jobs: + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: minsize + +- stage: windows_package_sign + displayName: 'Win Pkg Sign' + dependsOn: [windows_package_build] + jobs: + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: minsize + +- stage: linux_package + displayName: 'Linux Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: deb + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: deb-arm64 + jobName: deb_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' + packageType: rpm-fxdependent #mariner-x64 + jobName: mariner_x64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' + packageType: rpm-fxdependent-arm64 #mariner-arm64 + jobName: mariner_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: rpm + jobName: rpm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm' + signedDrop: 'drop_linux_sign_linux_arm' + packageType: tar-arm + jobName: tar_arm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: tar-arm64 + jobName: tar_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_alpine' + signedDrop: 'drop_linux_sign_linux_x64_alpine' + packageType: tar-alpine + jobName: tar_alpine + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd' + signedDrop: 'drop_linux_sign_linux_fxd' + packageType: fxdependent + jobName: fxdependent + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: tar + jobName: tar + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' + signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' + packageType: tar-alpine-fxdependent + jobName: tar_alpine_fxd + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_minSize' + signedDrop: 'drop_linux_sign_linux_x64_minSize' + packageType: min-size + jobName: minSize + +- stage: nupkg + displayName: 'NuGet Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/nupkg.yml@self + +- stage: msixbundle + displayName: 'MSIX Bundle+Sign' + dependsOn: [windows_package_build] # Only depends on unsigned packages + jobs: + - template: /.pipelines/templates/package-create-msix.yml@self + parameters: + OfficialBuild: ${{ parameters.OfficialBuild }} + +- stage: store_package + displayName: 'Store Package' + dependsOn: [msixbundle] + jobs: + - template: /.pipelines/templates/package-store-package.yml@self + +- stage: upload + displayName: 'Upload' + dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON + jobs: + - template: /.pipelines/templates/uploadToAzure.yml@self + +- stage: validatePackages + displayName: 'Validate Packages' + dependsOn: [upload] + jobs: + - template: /.pipelines/templates/release-validate-packagenames.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Release-Stages.yml b/.pipelines/templates/stages/PowerShell-Release-Stages.yml new file mode 100644 index 00000000000..52ce428a663 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Release-Stages.yml @@ -0,0 +1,323 @@ +parameters: + - name: releaseEnvironment + type: string + - name: SkipPublish + type: boolean + - name: SkipPSInfraInstallers + type: boolean + - name: skipMSIXPublish + type: boolean + +stages: +- stage: setReleaseTagAndChangelog + displayName: 'Set Release Tag and Upload Changelog' + jobs: + - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self + +- stage: validateSdk + displayName: 'Validate SDK' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "windowsSDK" + displayName: "Windows SDK Validation" + imageName: PSMMS2019-Secure + poolName: $(windowsPool) + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "MacOSSDK" + displayName: "MacOS SDK Validation" + imageName: macOS-latest + poolName: Azure Pipelines + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "LinuxSDK" + displayName: "Linux SDK Validation" + imageName: PSMMSUbuntu22.04-Secure + poolName: $(ubuntuPool) + +- stage: gbltool + displayName: 'Validate Global tools' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "WindowsGlobalTools" + displayName: "Windows Global Tools Validation" + jobtype: windows + + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "LinuxGlobalTools" + displayName: "Linux Global Tools Validation" + jobtype: linux + globalToolExeName: 'pwsh' + globalToolPackageName: 'PowerShell.Linux.x64' + +- stage: fxdpackages + displayName: 'Validate FXD Packages' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxd' + displayName: 'Validate Win Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependent' + packageNamePattern: '**/*win-fxdependent.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxdDesktop' + displayName: 'Validate WinDesktop Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' + packageNamePattern: '**/*win-fxdependentwinDesktop.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxfxd' + displayName: 'Validate Linux Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxArm64fxd' + displayName: 'Validate Linux ARM64 Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + # this is really an architecture independent package + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + arm64: 'yes' + enableCredScan: false + +- stage: ManualValidation + dependsOn: [] + displayName: Manual Validation + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate Windows Packages + jobName: ValidateWinPkg + instructions: | + Validate zip package on windows + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate OSX Packages + jobName: ValidateOsxPkg + instructions: | + Validate tar.gz package on osx-arm64 + +- stage: ReleaseAutomation + dependsOn: [] + displayName: 'Release Automation' + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start Release Automation + jobName: StartRA + instructions: | + Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Triage results + jobName: TriageRA + dependsOnJob: StartRA + instructions: | + Triage ReleaseAutomation results + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Signoff Tests + dependsOnJob: TriageRA + jobName: SignoffTests + instructions: | + Signoff ReleaseAutomation results + +- stage: UpdateChangeLog + displayName: Update the changelog + dependsOn: + - ManualValidation + - ReleaseAutomation + - fxdpackages + - gbltool + - validateSdk + + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure the changelog is updated + jobName: MergeChangeLog + instructions: | + Update and merge the changelog for the release. + This step is required for creating GitHub draft release. + +- stage: PublishGitHubReleaseAndNuget + displayName: Publish GitHub and Nuget Release + dependsOn: + - setReleaseTagAndChangelog + - UpdateChangeLog + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-githubNuget.yml@self + parameters: + skipPublish: ${{ parameters.SkipPublish }} + +- stage: PushGitTagAndMakeDraftPublic + displayName: Push Git Tag and Make Draft Public + dependsOn: PublishGitHubReleaseAndNuget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Push Git Tag + jobName: PushGitTag + instructions: | + Push the git tag to upstream + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make Draft Public + dependsOnJob: PushGitTag + jobName: DraftPublic + instructions: | + Make the GitHub Release Draft Public + +- stage: BlobPublic + displayName: Make Blob Public + dependsOn: + - UpdateChangeLog + - PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/release-MakeBlobPublic.yml@self + parameters: + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + +- stage: PublishPMC + displayName: Publish PMC + dependsOn: PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Publish to PMC + jobName: ReleaseToPMC + instructions: | + Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC + +- stage: UpdateDotnetDocker + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Update DotNet SDK Docker images + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Update .NET SDK docker images + jobName: DotnetDocker + instructions: | + Create PR for updating dotnet-docker images to use latest PowerShell version. + 1. Fork and clone https://github.com/dotnet/dotnet-docker.git + 2. git checkout upstream/nightly -b updatePS + 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas + 4. create PR targeting nightly branch + +- stage: UpdateWinGet + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Add manifest entry to winget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Add manifest entry to winget + jobName: UpdateWinGet + instructions: | + This is typically done by the community 1-2 days after the release. + +- stage: PublishMsix + dependsOn: + - setReleaseTagAndChangelog + - PushGitTagAndMakeDraftPublic + displayName: Publish MSIX to store + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-MSIX-Publish.yml@self + parameters: + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} + +- stage: PublishVPack + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release vPack + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start 2 vPack Release pipelines + jobName: PublishVPack + instructions: | + 1. Kick off PowerShell-vPack-Official pipeline + 2. Kick off PowerShell-MSIXBundle-VPack pipeline + +# Need to verify if the Az PS / CLI team still uses this. Skipping for this release. +# - stage: ReleaseDeps +# dependsOn: GitHubTasks +# displayName: Update pwsh.deps.json links +# jobs: +# - template: templates/release-UpdateDepsJson.yml + +- stage: UploadBuildInfoJson + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Upload BuildInfo.json + jobs: + - template: /.pipelines/templates/release-upload-buildinfo.yml@self + +- stage: ReleaseSymbols + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release Symbols + jobs: + - template: /.pipelines/templates/release-symbols.yml@self + +- stage: ChangesToMaster + displayName: Ensure changes are in GH master + dependsOn: + - PublishPMC + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure changes are in master + jobName: MergeToMaster + instructions: | + Make sure that changes README.md and metadata.json are merged into master on GitHub. + +- stage: ReleaseToMU + displayName: Release to MU + dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Release to MU + instructions: | + Notify the PM team to start the process of releasing to MU. + +- stage: ReleaseClose + displayName: Finish Release + dependsOn: + - ReleaseToMU + - ReleaseSymbols + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Retain Build + jobName: RetainBuild + instructions: | + Retain the build + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Delete release branch + jobName: DeleteBranch + instructions: | + Delete release branch diff --git a/.pipelines/templates/stages/PowerShell-vPack-Stages.yml b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml new file mode 100644 index 00000000000..01a83a5b161 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml @@ -0,0 +1,236 @@ +parameters: + - name: createVPack + type: boolean + - name: vPackName + type: string + +stages: +- stage: BuildStage + jobs: + - job: BuildJob + pool: + type: windows + + strategy: + matrix: + x86: + architecture: x86 + + x64: + architecture: x64 + + arm64: + architecture: arm64 + + variables: + ArtifactPlatform: 'windows' + ob_artifactBaseName: drop_build_$(architecture) + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_owneralias: tplunk + ob_createvpack_versionAs: parts + ob_createvpack_propsFile: true + ob_createvpack_verbose: true + ob_createvpack_packagename: '${{ parameters.vPackName }}.$(architecture)' + ob_createvpack_description: PowerShell $(architecture) $(version) + # I think the variables reload after we transition back to the host so this works. 🤷‍♂️ + ob_createvpack_majorVer: $(pwshMajorVersion) + ob_createvpack_minorVer: $(pwshMinorVersion) + ob_createvpack_patchVer: $(pwshPatchVersion) + ${{ if ne(variables['pwshPrereleaseVersion'], '') }}: + ob_createvpack_prereleaseVer: $(pwshPrereleaseVersion) + ${{ else }}: + ob_createvpack_prereleaseVer: $(Build.SourceVersion) + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $version = '$(Version)' + Write-Verbose -Verbose "Version: $version" + if(!$version) { + throw "Version is not set." + } + + $mainVersionParts = $version -split '-' + + Write-Verbose -Verbose "mainVersionParts: $($mainVersionParts[0]) ; $($mainVersionParts[1])" + $versionParts = $mainVersionParts[0] -split '[.]'; + $major = $versionParts[0] + $minor = $versionParts[1] + $patch = $versionParts[2] + + $previewPart = $mainVersionParts[1] + Write-Verbose -Verbose "previewPart: $previewPart" + + Write-Host "major: $major; minor: $minor; patch: $patch;" + + $vstsCommandString = "vso[task.setvariable variable=pwshMajorVersion]$major" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshMinorVersion]$minor" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshPatchVersion]$patch" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + if($previewPart) { + $vstsCommandString = "vso[task.setvariable variable=pwshPrereleaseVersion]$previewPart" + } else { + Write-Verbose -Verbose "No prerelease part found in version string." + } + displayName: Set ob_createvpack_*Ver + env: + ob_restore_phase: true + + # Validate pwsh*Version variables + - pwsh: | + $variables = @("pwshMajorVersion", "pwshMinorVersion", "pwshPatchVersion") + foreach ($var in $variables) { + if (-not (get-item "Env:\$var" -ErrorAction SilentlyContinue).value) { + throw "Required variable '`$env:$var' is not set." + } + } + displayName: Validate pwsh*Version variables + env: + ob_restore_phase: true + + - pwsh: | + if($env:RELEASETAGVAR -match '-') { + throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + version: 3.1.x + installationPath: $(Agent.ToolsDirectory)/dotnet + + ### BUILD ### + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(repoRoot) + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + AnalyzeInPipeline: false # Do not upload results + Language: csharp + + - task: UseDotNet@2 + displayName: 'Install .NET based on global.json' + inputs: + useGlobalJson: true + workingDirectory: $(repoRoot) + env: + ob_restore_phase: true + + - pwsh: | + # Need to set PowerShellRoot variable for obp-file-signing template + $vstsCommandString = "vso[task.setvariable variable=PowerShellRoot]$(repoRoot)" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $Architecture = '$(Architecture)' + $runtime = switch ($Architecture) + { + "x64" { "win7-x64" } + "x86" { "win7-x86" } + "arm64" { "win-arm64" } + } + + $params = @{} + if ($env:BuildConfiguration -eq 'minSize') { + $params['ForMinimalSize'] = $true + } + + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(repoRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/Symbols_$Architecture" -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + $ReleaseTagParam = @{} + + if ($env:RELEASETAGVAR) { + $ReleaseTagParam['ReleaseTag'] = $env:RELEASETAGVAR + } + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore @params @ReleaseTagParam + + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: Build Windows Universal - $(Architecture) -$(BuildConfiguration) Symbols folder + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(repoRoot)\src' + ob_restore_phase: true + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + SigningProfile: $(windows_build_tools_cert_id) + OfficialBuild: false + vPackScenario: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem env:/ob_createvpack_*Ver + Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + Get-Content "$(Pipeline.Workspace)\PowerShell\preview.json" -ErrorAction SilentlyContinue | Write-Host + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - pwsh: | + $vpackFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(Pipeline.Workspace)\Symbols_$(Architecture)" + } + $vpackFiles + displayName: Debug Output Directory and Version + condition: succeededOrFailed() diff --git a/.pipelines/templates/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..3a6bec4a859 --- /dev/null +++ b/.pipelines/templates/testartifacts.yml @@ -0,0 +1,134 @@ +jobs: +- job: build_testartifacts_win + variables: + - 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/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(RepoRoot) + ob_restore_phase: true + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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: 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/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(Build.SourcesDirectory)/PowerShell + ob_restore_phase: true + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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..de6f8e0d7fd --- /dev/null +++ b/.pipelines/templates/uploadToAzure.yml @@ -0,0 +1,425 @@ +jobs: +- job: upload_packages + displayName: Upload packages + condition: succeeded() + pool: + type: windows + variables: + - name: ob_sdl_sbom_enabled + value: true + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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: 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: | + **/*.msix + **/*.zip + 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: | + **/*.msix + **/*.zip + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_package_win_x86 + itemPattern: | + **/*.msix + **/*.zip + 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", "*.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/variables/PowerShell-Coordinated_Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml new file mode 100644 index 00000000000..dd67d509a8a --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml @@ -0,0 +1,67 @@ +parameters: + - name: InternalSDKBlobURL + type: string + default: ' ' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + type: string + default: 'NO' + - name: ENABLE_MSBUILD_BINLOGS + type: boolean + default: false + - name: FORCE_CODEQL + type: boolean + default: false + +variables: + - name: PS_RELEASE_BUILD + value: 1 + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - name: BUILDSECMON_OPT_IN + value: true + - name: __DOTNET_RUNTIME_FEED + value: ${{ parameters.InternalSDKBlobURL }} + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: WindowsContainerImage + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: SKIP_SIGNING + value: ${{ parameters.SKIP_SIGNING }} + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: ENABLE_MSBUILD_BINLOGS + value: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: + # Cadence is hours before CodeQL will allow a re-upload of the database + - name: CodeQL.Cadence + value: 1 + - name: CODEQL_ENABLED + ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: + value: true + ${{ else }}: + value: false + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true + # Disable BinSkim at job level to override NonOfficial template defaults + - name: ob_sdl_binskim_enabled + value: false diff --git a/.pipelines/templates/variables/PowerShell-Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml new file mode 100644 index 00000000000..7d1818909b5 --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml @@ -0,0 +1,50 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ForceAzureBlobDelete + type: string + default: 'false' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: disableNetworkIsolation + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] # needed for onebranch.pipeline.version task + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: ForceAzureBlobDelete + value: ${{ parameters.ForceAzureBlobDelete }} + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - group: MSIXSigningProfile + - name: disableNetworkIsolation + value: ${{ parameters.disableNetworkIsolation }} diff --git a/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml new file mode 100644 index 00000000000..3b47e5eff2b --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml @@ -0,0 +1,35 @@ +parameters: + - name: debug + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\.config\tsaoptions.json + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: PoolNames diff --git a/.pipelines/templates/variables/PowerShell-Release-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Variables.yml new file mode 100644 index 00000000000..930c559eafe --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Variables.yml @@ -0,0 +1,41 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: PoolNames + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true diff --git a/.pipelines/templates/variables/PowerShell-vPack-Variables.yml b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml new file mode 100644 index 00000000000..7f00a5e0e2a --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml @@ -0,0 +1,39 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: netiso + type: string + default: 'R1' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: Azure Blob variable group + - group: certificate_logical_to_actual # used within signing task + - group: DotNetPrivateBuildAccess + - name: netiso + value: ${{ parameters.netiso }} +# We shouldn't be using PATs anymore +# - group: mscodehub-feed-read-general diff --git a/.pipelines/templates/variables/release-shared.yml b/.pipelines/templates/variables/release-shared.yml new file mode 100644 index 00000000000..70d3dd2df97 --- /dev/null +++ b/.pipelines/templates/variables/release-shared.yml @@ -0,0 +1,40 @@ +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: DOTNET_NOLOGO + 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..0f7c1f8fb2e --- /dev/null +++ b/.pipelines/templates/windows-hosted-build.yml @@ -0,0 +1,323 @@ +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: NugetSecurityAnalysisWarningLevel + value: none + - name: DOTNET_NOLOGO + 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 + + - template: /.pipelines/templates/install-dotnet.yml@self + + - 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 + Find-Dotnet + + ## 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)' + OfficialBuild: $(ps_official_build) + + ## 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' + OfficialBuild: $(ps_official_build) + + - 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. + #> + + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + Find-Dotnet + + $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' + 'Microsoft.PowerShell.ThreadJob' + ) + + $sourceModulePath = Join-Path '$(GlobalToolArtifactPath)' 'publish' 'PowerShell.Windows.x64' 'release' 'Modules' + $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.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\net11.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\net11.0\any\*.pdb" -Recurse -Force + + # create powershell.config.json + $config = [ordered]@{} + $config.Add("Microsoft.PowerShell:ExecutionPolicy", "RemoteSigned") + $config.Add("WindowsPowerShellCompatibilityModuleDenyList", @("PSScheduledJob", "BestPractices", "UpdateServices")) + + $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' "powershell.config.json" + Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop + + Compress-Archive -Path "$outputPath\temp\*" -DestinationPath "$outputPath\$nupkgName" -Force + + 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: '$(nuget_cert_id)' + 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/.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 51b4d8435fd..cc711f0aa5a 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 @@ -25,7 +30,10 @@ adhoc aditya adityapatwardhan ADOPTERS.md +aetos382 aiello +Aishat452 +al-cheb alepauly alexandair alexjordan6 @@ -44,6 +52,7 @@ alpha.9 alternatestream alvarodelvalle amd64 +ananya26-vishnoi andschwa anmenaga api @@ -52,21 +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 @@ -78,38 +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 @@ -127,10 +163,14 @@ CodeFormatter codeowner codepage commanddiscovery +CommandInvocationIntrinsics commandsearch CommandSearcher comobject +Compiler.cs composability +computerinfo +ComRuntimeHelpers.cs config connect-pssession consolehost @@ -158,6 +198,8 @@ CorePsAssemblyLoadContext.cs coveralls.exe coveralls.io. coveralls.net +CreateFile +CreateFileW credssp cron crontab @@ -169,10 +211,13 @@ csmacnz csphysicallyinstalledmemory ctrl CurrentCulture +CustomShellCommands.cs DamirAinullin +DarylGraves darquewarrior darwinjs DateTime +DateTime.UnixEpoch daxian-dbw dayofweek dchristian3188 @@ -194,6 +239,7 @@ devcontainer deviceguard devlead devops +dgoldman-msft Dictionary.TryAdd diddledan disable-localuser @@ -207,28 +253,33 @@ 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 ece-jacob-scott -EditorConfig +editorconfig edyoung enable-localuser enable-psbreakpoint @@ -247,6 +298,7 @@ ergo3114 errorrecord etl eugenesmlv +EventLogLogProvider excludeversion exe executables @@ -258,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 @@ -285,6 +344,7 @@ functionprovider FunctionTable fxdependent gabrielsroka +GAC_Arm64 gamified gc.regions.xml Generic.SortedList @@ -325,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 @@ -358,7 +426,9 @@ httpbin's https hubuk hvitved +i3arnon i.e. +ico idera IDictionary ifdef'ed @@ -374,14 +444,19 @@ includeide includeusername informationrecord initializers +InitialSessionState.cs +InlineAsTypeCheck install-packageprovider +IntelliSense interactivetesting interop interoperation +interlocked.read invoke-cimmethod Invoke-DSCResource invoke-restmethod invoke-wsmanaction +InvokeRestMethodCommand.Common iot isazonov iscore @@ -401,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 @@ -434,6 +514,9 @@ launch.json ldspits lee303 Leonhardt +Libera.Chat +libicu +LibraryImport libpsl libpsl-native libunwind8 @@ -449,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 @@ -469,13 +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 @@ -490,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 @@ -513,11 +614,15 @@ mwrock myget namedpipe nameof +NameObscurerTelemetryInitializer namespace nano nanoserver +NativeCommandProcessor.cs +NativeCultureResolver nativeexecution net5.0 +net10.0 netcoreapp5.0 netip.ps1. netstandard.dll @@ -543,11 +648,15 @@ new-winevent new-wsmaninstance new-wsmansessionoption NextTurn +ngharo +Newtonsoft.Json NJsonSchema +nohwnd NoMoreFood non-22 non-cim non-https +non-nullable non-r2 noresume notcontains @@ -560,6 +669,7 @@ numberbytes numberOfPowershellRefAssemblies nupkg oauth +object.ReferenceEquals offthewoll oising omi @@ -573,6 +683,7 @@ openssh openssl opensuse oss +OutputType p1 packagemanagement PackageVersion @@ -580,8 +691,11 @@ parameshbabu parameterbinderbase parameterbindercontroller parameterbinding +ParenExpression ParseError.ToString +Path.Join pathresolution +PathResolvedToMultiple patochun patwardhan paulhigin @@ -590,7 +704,11 @@ payette perf perfview perfview.exe +peter-evans petseral +PingPathCommand.cs +pinvoke +pinvokes plaintext pluggable pluralsight @@ -611,12 +729,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 @@ -624,6 +745,7 @@ prepend preprocessor preview.1 preview.2 +preview.2.22153.17 preview.3 preview.4 preview.5 @@ -632,13 +754,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 @@ -657,8 +792,10 @@ PSGalleryModules psm1 psobject psobjects +psoptions.json psproxyjobs psreadline +psresourceget psrp.windows psscriptanalyzer pssessionconfiguration @@ -672,26 +809,35 @@ 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 +rcedit readme readme.md readonly ReadyToRun rebase +rebase.yml rebasing receive-pssession recurse reddit redhat +redirections redistributable redistributables register-argumentcompleter @@ -702,6 +848,8 @@ register-packagesource register-psrepository registryprovider relationlink +releaseTools.psm1 +RemoteSessionNamedPipe remotesigned remoting remove-ciminstance @@ -739,15 +887,21 @@ rkitover robo210 ronn rpalo +rpolley +runas runspace runspaceinit runspaces runtime runtimes +Ryan-Hutchison-USAF +SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation Saancreed +SafeRegistryHandle sample-dotnet1 sample-dotnet2 sarithsutha +sarthakmalik savehelp sazonov sba923 @@ -757,6 +911,7 @@ scriptblock securestring seemethere select-xml +SemanticChecks semver serverless sessionid @@ -779,13 +934,16 @@ set-wsmaninstance set-wsmanquickconfig sethvs setversionvariables +sha256 ShaydeNofziger shellexecute shouldbeerrorid showcommandinfo +Shriram0908 silijon simonwahlin singleline +sles15 smes snapcraft snapin @@ -800,8 +958,10 @@ st0le stackoverflow stanzilla start-codecoveragerun +start-pspester stdin stevel-msft +StevenLiekens stevend811 stknohg strawgate @@ -811,11 +971,14 @@ string.split stringbuilder stuntguy3000 StyleCop +subfolder submodule submodules sudo superproject +svg swarfegagit +SwitchParameter sxs sydneyhsmith symlink @@ -826,15 +989,18 @@ System.IO.Packaging System.InvalidOperationException system.manage system.management.automation +System.Management.Automation.utils systemd SytzeAndr tabcompletion tadas tandasat +tasnimzotder TargetFramework test-modulemanifest test-pssessionconfigurationfile test-scriptfileinfo +tar.gz test.ps1 test.txt. Tests.ps1 @@ -842,6 +1008,7 @@ test1.txt test2.txt testcase testdrive +TestPathCommand.cs tests.zip tgz theflyingcorpse @@ -868,7 +1035,10 @@ toolset tracesource travisez13 travisty +trossr32 truher +TSAUpload +turbedi TValue tylerleonhardt typecataloggen @@ -877,8 +1047,11 @@ typegen typematch t_ ubuntu +ubuntu22.04 +uint un-versioned unicode +UnixSocket unregister-event unregister-packagesource unregister-psrepository @@ -886,12 +1059,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 @@ -906,6 +1082,7 @@ v0.4.0 v0.5.0 v0.6.0 v141 +v2 v3 v4 v5 @@ -926,11 +1103,25 @@ v6.2.2 v6.2.3 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 @@ -942,6 +1133,8 @@ walkthrough webcmdlets weblistener webrequest +webrequestpscmdlet.common.cs +webresponseobject.common weltner wesholton84 wget @@ -952,11 +1145,17 @@ wildcards win32 win32-openssh win7 +win8 windos +windows.json windowspsmodulepath windowsversion winrm wix +wmentha +WNetGetConnection +WNetAddConnection2 +worrenb wpr wprui.exe writingpestertests.md @@ -977,7 +1176,177 @@ yashrajbharti yecril71pl yml youtube +Youssef1313 +Yulv-git zackjknight +ComInterop +ryneandal +runtime#33060 +vexx32 +perf +britishben +felixfbecker +vpondala +dependabot +jellyfrog +1redone +tommymaynard +vmsilvamolina +fbehrens +lockdown +lukexjeremy +deserializing +kiazhi +v6.1.2 +Menagarishvili +anmenaga +fxdependent +sba923 +replicaJunction +lupino3 +hvitved +unvalidated +Geweldig +mjanko5 +v7.0.0 +v7.0.10 +renehernandez +ece-jacob-scott +st0le +MohiTheFish +CodeFormatter +StyleCop +SytzeAndr +yashrajbharti +Leonhardt +tylerleonhardt +nuget.config +libmi +rpms +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 @@ -1017,6 +1386,7 @@ weltkante kilasuit tnieto88 Orca88 +OrderBy centreboard romero126 Greg-Smulko @@ -1046,7 +1416,9 @@ Francisco-Gamino adamdriscoll analytics deserialized +string.Join string.Split +StringSplitOptions.TrimEntries Dictionary.TryAdd Environment.NewLine ParseError.ToString @@ -1069,6 +1441,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 @@ -1094,9 +1495,263 @@ wpa wpaProfile - demos/WindowsPowerShellModules/README.md 2.x. - - CHANGELOG/preview.md + - CHANGELOG/7.1.md ThomasNieto spongemike2 davidseibel HumanEquivalentUnit +hemisphera +tamasvajk +boolean +bitwise +StringUtil +StringUtil.Format +CommandLineParameterParser +jackerr3 +preview.8.20407.11 +pre-check +davidreis97 +soccypowa +nologo +InstallLocation +PkgES +Microsoft.PowerShell.Native +rtm.20526.5 +ini +package.json jcotton42 +RPMs +PSDesiredStateConfiguration +dotnet5 + - CHANGELOG/7.2.md +Gimly +jborean93 +mkswd +PatLeong +paul-cheung +georgettica +ProcessId +CurrentManagedThreadId +System.Environment +LongCount +nullable +Guid +accessor +CancellationToken +struct +IsEmpty +StringBuilder.Append +String.Concat +String.Substring +NoLanguage +kyanha +accessors +matthewjdegarmo +Reset-PWSHSystemPath +StyleCop.Analyzers +UseIsNullCheck +ConvertTypeOfToNameOf +usings +PriorityAttribute +System.Management.Automation.Interpreter.IBoxableInstruction +System.Management.Automation.Provider.IDynamicPropertyProvider +System.Management.Automation.Language.IScriptExtent +System.Management.Automation.Language.ICustomAstVisitor2 +System.Management.Automation.LanguagePrimitives.IConversionData +System.Automation.Remoting.Client.IWSManNativeApiFacade +System.Management.Automation.Language.ISupportsAssignment +System.Management.Automation.ICommandRuntime2 +System.Management.Automation.IOutputProcessingState +System.Management.Automation.IJobDebugger +System.Management.Automation.Interpreter.IInstructionProvider +System.Management.Automation.IHasSessionStateEntryVisibility +System.Management.Automation.Tracing.IEtwEventCorrelator +AclCommands +System.Management.Automation.Language.IAstPostVisitHandler +System.Management.Automation.IModuleAssemblyInitializer +nuget.org +GetFiles +TestModuleManifestCommand +System.Management.Automation.Provider.IContentWriter +TranscriptionOption.FlushContentToDisk +structs +System.Management.Automation.IArgumentCompleter +GetDirectories +System.Management.Automation.Host.IHostSupportsInteractiveSession +System.Management.Automation.Provider.IPropertyCmdletProvider +SimplifyConditionalExpression +MarkdownLint +AsSpan +AsMemory +xml +finalizer +finalizers +const +UseAutoProperty +SuppressFinalize +string.Empty +LoadBinaryModule +GetListOfFilesFromData +RPMs +NullableAttribute +PSDesiredStateConfiguration +brianary +Fs00 +MartinGC94 +jakekerr +strikethrough +string.Contains +imba-tjd +url +ArrayList +SDKs +XunitXml.TestLogger +Backport +v7.1.1 +about_PSDesiredStateConfiguration +hbuckle +preview.2.21155.3 +3.final +codesign +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/.vscode/extensions.json b/.vscode/extensions.json index 683a979ec46..fa227cd9944 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ // See https://go.microsoft.com/fwlink/?LinkId=827846 // for the documentation about the extensions.json format "recommendations": [ + "editorconfig.editorconfig", "ms-azure-devops.azure-pipelines", "ms-vscode.cpptools", "ms-dotnettools.csharp", 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..10effadd1e3 100644 --- a/.vsts-ci/linux-daily.yml +++ b/.vsts-ci/linux-daily.yml @@ -18,23 +18,14 @@ 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 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 resources: @@ -47,7 +38,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 +46,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 +141,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..b90ab0d9eb4 --- /dev/null +++ b/.vsts-ci/linux-internal.yml @@ -0,0 +1,115 @@ +# Pipeline to run Linux CI internally +name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) +trigger: + # Batch merge builds together while a merge build is running + batch: true + branches: + include: + - master + - release* + - feature* + paths: + include: + - '*' + exclude: + - .vsts-ci/misc-analysis.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .dependabot/config.yml + - .pipelines/* + - test/perf/* +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - '*' + exclude: + - .dependabot/config.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .vsts-ci/misc-analysis.yml + - .vsts-ci/windows.yml + - .vsts-ci/windows/* + - tools/cgmanifest/* + - LICENSE.txt + - test/common/markdown/* + - test/perf/* + - tools/releaseBuild/* + - tools/install* + - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling + - .pipelines/* + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +resources: + repositories: + - repository: Docker + type: github + endpoint: PowerShell + name: PowerShell/PowerShell-Docker + ref: master + +stages: +- stage: BuildLinuxStage + displayName: Build for Linux + jobs: + - template: templates/ci-build.yml + parameters: + pool: ubuntu-20.04 + jobName: linux_build + displayName: linux Build + +- stage: TestUbuntu + displayName: Test for Ubuntu + dependsOn: [BuildLinuxStage] + jobs: + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: UnelevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: ElevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: UnelevatedPesterTests + tagSet: Others + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: ElevatedPesterTests + tagSet: Others + + - template: templates/verify-xunit.yml + parameters: + pool: ubuntu-20.04 + +- stage: PackageLinux + displayName: Package Linux + dependsOn: ["BuildLinuxStage"] + jobs: + - template: linux/templates/packaging.yml + parameters: + pool: ubuntu-20.04 diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml index 3b934fccb4a..5d9dc663e1c 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,80 +34,46 @@ pr: - feature* paths: include: - - '*' - exclude: - - tools/releaseBuild/* - - tools/releaseBuild/azureDevOps/templates/* - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - .vsts-ci/linux.yml + - .vsts-ci/linux/templates/packaging.yml + - assets/manpage/* + - build.psm1 + - global.json + - nuget.config + - PowerShell.Common.props + - src/*.csproj + - tools/ci.psm1 + - tools/packaging/* variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none 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 - 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 + - template: linux/templates/packaging.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 445c0e3f463..678ded65259 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,20 +28,31 @@ pr: include: - '*' exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml + - .dependabot/config.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .vsts-ci/misc-analysis.yml + - .vsts-ci/windows.yml + - .vsts-ci/windows/* + - tools/cgmanifest/* + - LICENSE.txt + - test/common/markdown/* + - test/perf/* + - tools/packaging/* - tools/releaseBuild/* - 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 + DOTNET_NOLOGO: 1 # Turn off Homebrew analytics HOMEBREW_NO_ANALYTICS: 1 __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none resources: - repo: self @@ -81,16 +95,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: | - 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 327e5528107..00000000000 --- a/.vsts-ci/misc-analysis.yml +++ /dev/null @@ -1,75 +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: -- repo: self - clean: true -jobs: -- template: templates/credscan.yml - -- job: Linux_CI - - displayName: Markdown and Common Tests - - pool: - name: Hosted Ubuntu 1604 - steps: - - 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() - - - powershell: 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') - - - bash: | - mdspell '**/*.md' '!**/Pester/**/*.md' --ignore-numbers --ignore-acronyms --report --en-us; - displayName: Test Spelling in Markdown - condition: succeededOrFailed() - - - powershell: | - 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() - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)' - snapshotForceEnabled: true 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..225e2699533 --- /dev/null +++ b/.vsts-ci/psresourceget-acr.yml @@ -0,0 +1,155 @@ +# 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/* + - 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 + DOTNET_NOLOGO: 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..72c5710016b 100644 --- a/.vsts-ci/sshremoting-tests.yml +++ b/.vsts-ci/sshremoting-tests.yml @@ -23,23 +23,34 @@ pr: - '/test/SSHRemoting/*' 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 + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: DOTNET_NOLOGO + 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 026448908f6..5ec458c3c5a 100644 --- a/.vsts-ci/templates/ci-build.yml +++ b/.vsts-ci/templates/ci-build.yml @@ -1,41 +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 - - powershell: | - [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 + - 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: succeededOrFailed() + 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 deleted file mode 100644 index c989d01c2f8..00000000000 --- a/.vsts-ci/templates/nanoserver.yml +++ /dev/null @@ -1,61 +0,0 @@ -parameters: - vmImage: 'windows-2019' - jobName: 'Nanoserver_Tests' - continueOnError: false - -jobs: - -- job: ${{ parameters.jobName }} - variables: - scriptName: ${{ parameters.scriptName }} - - pool: - vmImage: ${{ parameters.vmImage }} - - displayName: ${{ parameters.jobName }} - - steps: - - script: | - set - displayName: Capture Environment - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - pwsh: | - Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 - displayName: 'Install Pester' - continueOnError: true - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - Write-Verbose "Path: '$path'" -Verbose - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-Pester -Path ./test/nanoserver -OutputFormat NUnitXml -OutputFile ./test-nanoserver.xml - displayName: Test - condition: succeeded() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - displayName: Publish Nanoserver Test Results **\test*.xml - inputs: - testRunner: NUnit - testResultsFiles: '**\test*.xml' - testRunTitle: nanoserver - mergeTestResults: true - failTaskOnFailedTests: true diff --git a/.vsts-ci/templates/nix-test.yml b/.vsts-ci/templates/nix-test.yml index 0938a93bcf9..214ae14b2c6 100644 --- a/.vsts-ci/templates/nix-test.yml +++ b/.vsts-ci/templates/nix-test.yml @@ -1,72 +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: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall -SkipUser - displayName: Bootstrap - condition: succeededOrFailed() - - - 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-packaging.yml b/.vsts-ci/templates/windows-packaging.yml deleted file mode 100644 index df422b812d5..00000000000 --- a/.vsts-ci/templates/windows-packaging.yml +++ /dev/null @@ -1,34 +0,0 @@ -parameters: - pool: 'Hosted VS2017' - jobName: 'win_packaging' - parentJobs: [] - -jobs: -- job: ${{ parameters.jobName }} - dependsOn: - ${{ parameters.parentJobs }} - pool: - name: ${{ parameters.pool }} - - displayName: Windows Packaging - - steps: - - powershell: | - Get-ChildItem -Path env: - displayName: Capture environment - condition: succeededOrFailed() - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - - - powershell: | - [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - condition: succeededOrFailed() - - - powershell: | - Import-Module .\tools\ci.psm1 - New-CodeCoverageAndTestPackage - Invoke-CIFinish -NuGetKey $(NUGET_KEY) - displayName: Build and Test Package diff --git a/.vsts-ci/templates/windows-test.yml b/.vsts-ci/templates/windows-test.yml deleted file mode 100644 index a153b563e3f..00000000000 --- a/.vsts-ci/templates/windows-test.yml +++ /dev/null @@ -1,51 +0,0 @@ -parameters: - pool: 'Hosted VS2017' - parentJobs: [] - purpose: '' - tagSet: 'CI' - -jobs: -- job: win_test_${{ parameters.purpose }}_${{ parameters.tagSet }} - dependsOn: - ${{ parameters.parentJobs }} - pool: - name: ${{ parameters.pool }} - - displayName: Windows 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 - - # must be run frow Windows PowerShell - - powershell: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-CITest -Purpose '${{ parameters.purpose }}' -TagSet '${{ parameters.tagSet }}' - displayName: Test - condition: succeeded() diff --git a/.vsts-ci/windows-arm64.yml b/.vsts-ci/windows-arm64.yml new file mode 100644 index 00000000000..1c4bc2ee8af --- /dev/null +++ b/.vsts-ci/windows-arm64.yml @@ -0,0 +1,94 @@ +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/* + - 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 + - name: DOTNET_NOLOGO + 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 deleted file mode 100644 index 6a88011dff8..00000000000 --- a/.vsts-ci/windows-daily.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) -trigger: - # Batch merge builds together while a merge build is running - batch: true - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml -pr: - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml - -variables: - GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - __SuppressAnsiEscapeSequences: 1 - -resources: -- repo: self - clean: true - -stages: -- stage: BuildWin - displayName: Build for Windows - jobs: - - template: templates/ci-build.yml - -- stage: TestWin - displayName: Test for Windows - variables: - - group: CLR-CAP - jobs: - - job: win_test - pool: - vmImage: vs2017-win2016 - displayName: Windows Test - - steps: - - pwsh: | - Get-ChildItem -Path env: - displayName: 'Capture Environment' - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - xunit/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - 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: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $path = Split-Path -Parent (Get-PSOutput -Options (Get-PSOptions)) - $rootPath = Split-Path -Path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - displayName: 'Unzip Build' - condition: succeeded() - - - pwsh: | - Import-Module $(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 .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet CI - displayName: Test - UnelevatedPesterTests - CI - condition: succeeded() - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet CI - displayName: Test - ElevatedPesterTests - CI - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet Others - displayName: Test - UnelevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet Others - displayName: Test - ElevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - $xUnitTestResultsFile = '$(System.ArtifactsDirectory)\xunit\xUnitTestResults.xml' - Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile - displayName: Verify xUnit Test Results - condition: succeededOrFailed() - - - 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() - -- stage: PackagingWin - displayName: Packaging for Windows - jobs: - # Unlike daily builds, we do not upload nuget package to MyGet so we do not wait on tests to finish. - - template: templates/windows-packaging.yml diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml index 11bccbbbfa6..4171d09643d 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,21 +25,27 @@ 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'" DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 + NugetSecurityAnalysisWarningLevel: none + nugetMultiFeedWarnLevel: none resources: - repo: self @@ -72,11 +81,3 @@ stages: tagSet: Others - template: templates/verify-xunit.yml - -- stage: PackagingWin - displayName: Packaging for Windows - dependsOn: [] # by specifying an empty array, this stage doesn't depend on the stage before it - jobs: - # Unlike daily builds, we do not upload nuget package to MyGet so we do not wait on tests to finish. - - template: templates/windows-packaging.yml - diff --git a/.vsts-ci/windows/templates/windows-packaging.yml b/.vsts-ci/windows/templates/windows-packaging.yml new file mode 100644 index 00000000000..d23b745c30f --- /dev/null +++ b/.vsts-ci/windows/templates/windows-packaging.yml @@ -0,0 +1,111 @@ +parameters: + - 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 }} + + variables: + - name: repoFolder + value: PowerShell + - name: repoPath + value: $(Agent.BuildDirectory)\$(repoFolder) + - name: complianceRepoFolder + value: compliance + - name: complianceRepoPath + value: $(Agent.BuildDirectory)\$(complianceRepoFolder) + + 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: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment + condition: succeededOrFailed() + + - 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 ${{ 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 new file mode 100644 index 00000000000..6b73ca05723 --- /dev/null +++ b/.vsts-ci/windows/windows-packaging.yml @@ -0,0 +1,87 @@ +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: + exclude: + - tests/* + - docs/* + - demos/* + - CHANGELOG/* + - .devcontainer/* + - .github/* + - .poshchan/* + - .vscode/* + - code-server/* + - docker/* + +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - .vsts-ci/windows/*.yml + - assets/wix/* + - build.psm1 + - global.json + - nuget.config + - PowerShell.Common.props + - src/*.csproj + - test/packaging/windows/* + - tools/ci.psm1 + - tools/packaging/* + - tools/wix/* + +variables: + - name: GIT_CONFIG_PARAMETERS + value: "'core.autocrlf=false'" + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: DOTNET_NOLOGO + value: 1 + - name: __SuppressAnsiEscapeSequences + value: 1 + - group: fakeNugetKey + - name: SBOMGenerator_Formats + value: spdx:2.2 + - name: nugetMultiFeedWarnLevel + value: none + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: PowerShell + name: PowerShell/compliance + ref: master + +stages: +- stage: PackagingWin + displayName: Packaging for Windows + dependsOn: [] # by specifying an empty array, this stage doesn't depend on the stage before it + jobs: + # Unlike daily builds, we do not upload nuget package to MyGet so we do not wait on tests to finish. + - template: templates/windows-packaging.yml + - template: templates/windows-packaging.yml + parameters: + channel: stable + architecture: x86 + - template: templates/windows-packaging.yml + 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 182d36dd37f..186b99dd093 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -10,27 +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 Dashboard](https://ironmansoftware.com/powershell-universal-dashboard) 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 new file mode 100644 index 00000000000..6f906496c73 --- /dev/null +++ b/Analyzers.props @@ -0,0 +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 e80c0b179fc..e054b34cfc9 100644 --- a/CHANGELOG/7.0.md +++ b/CHANGELOG/7.0.md @@ -1,5 +1,353 @@ # 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 + +- Fix web cmdlets to properly construct URI from body when using `-NoProxy` (#14673) +- Fix `PromptForCredential()` to add `targetName` as domain (#14504) +- Clean up the IPC named pipe on PowerShell exit (#12187) + +### Tests + +- Update markdown test packages with security fixes (#13730, #14145, #14454) + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to version 3.1.407 + + +
    +
  • Bump .NET to version 3.1.407 (Internal 14783)
  • +
  • Fix the miscellaneous analysis CI build (#14971, #14974, #14975)
  • +
  • Declare which variable group is used for checking the blob in the release build (#14970)
  • +
  • Use template that disables component governance for CI (#14938)
  • +
  • Suppress the warning for having multiple nuget feeds (#14893)
  • +
  • Disable codesign validation where the file type is not supported (#14885)
  • +
  • Make universal Deb package based on deb package spec (#14681)
  • +
  • Add manual release automation steps and improve changelog script (#14445)
  • +
  • Fix a typo in the Get-ChangeLog function (#14129)
  • +
  • Add validation and dependencies for Ubuntu 20.04 distribution to packaging script (#13993)
  • +
  • Add comment in release-BuildJson.yml for date formatting
  • +
  • Install wget on centos-7 docker image
  • +
  • Fix install-dotnet download (#14856)
  • +
  • Fix release build to upload global tool packages to artifacts (#14620)
  • +
  • Fixes to release pipeline for GA release (#14034)
  • +
  • Add checkout step to release build templates (#13840)
  • +
  • Add flag to make Linux script publish to production repo (#13714)
  • +
  • Use new release script for Linux packages (#13705)
  • +
  • Change stage dependency for docker release stage in release pipeline (#13512)
  • +
  • Create the folder before copying the global tools (#13476)
  • +
  • A few fixes to the release pipeline (#13473)
  • +
  • Change the variable group name (Internal 12339)
  • +
  • Create release pipeline as a yaml pipeline (#13394)
  • +
+ +
+ +[7.0.6]: https://github.com/PowerShell/PowerShell/compare/v7.0.5...v7.0.6 + +## [7.0.5] - 2021-02-11 + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to version 3.1.406 + + +
    +
  • Fix third party signing for files in sub-folders (#14751)
  • +
  • Bump .NET SDK to 3.1.12 (Internal 14462)
  • +
+ +
+ +[7.0.5]: https://github.com/PowerShell/PowerShell/compare/v7.0.4...v7.0.5 + +## [7.0.4] - 2021-01-19 + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to version 3.1.405 + + +
    +
  • Remove MyGet feeds from test nuget.config (Internal 14147)
  • +
  • Update WXS file for 7.0.4 (Internal 14122)
  • +
  • Update .NET dependencies for 7.0.4 (Internal 14104)
  • +
  • Fix 7.0.4 `Get-Module` test failure (Internal 13946)
  • +
  • Fix directory creation failure (Internal 13904)
  • +
  • Disable WMF link invocation test (#13479)
  • +
  • Use PowerShell Core for build and test of package in CI build (#13223)
  • +
  • Disable libmi dependent tests for macOS. (#14446)
  • +
  • Use one feed in each nuget.config in official builds (#14363)
  • +
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • +
  • Fix syntax error in Windows packaging script (#14377)
  • +
  • Make AppLocker Enforce mode take precedence over UMCI Audit mode (#14353)
  • +
  • Fix issue with unsigned build (#14367)
  • +
  • Move macOS and NuGet to ESRP signing (#14324)
  • +
  • Move Windows package signing to use ESRP (#14060)
  • +
  • Move Linux to ESRP signing (#14210)
  • +
  • Migrate 3rd party signing to ESRP (#14010)
  • +
  • Don't do a shallow checkout (#13992)
  • +
  • Move to ESRP signing for Windows files (#13988)
  • +
  • Fix breaks in packages daily build due to macOS signing changes (#13421)
  • +
  • Sign individual files in package (#13392)
  • +
  • Use Authenticode certificate for MSIX signing (#13330)
  • +
  • Sign the MSIX files for the store (#12582)
  • +
  • Use temporary personal path at runspace startup when $env:HOME not defined (#13239)
  • +
  • Fix MSIX packaging to determine if a preview release by inspecting the semantic version string (#11991)
  • +
  • Add default help content to the assets folder (#13257)
  • +
+ +
+ +[7.0.4]: https://github.com/PowerShell/PowerShell/compare/v7.0.3...v7.0.4 + ## [7.0.3] - 2020-07-16 ### Tests @@ -18,6 +366,8 @@ +[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 @@ -196,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 @@ -1056,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 new file mode 100644 index 00000000000..eba1998e29c --- /dev/null +++ b/CHANGELOG/7.1.md @@ -0,0 +1,1158 @@ +# 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 + +- Remove the 32K character limit on the environment block for `Start-Process` (#14111) +- Fix webcmdlets to properly construct URI from body when using `-NoProxy` (#14673) + +### General Cmdlet Updates and Fixes + +- Fix `PromptForCredential()` to add `targetName` as domain (#14504) + +### Build and Packaging Improvements + +
+ + + +Bump .NET SDK to 5.0.4 + + + +
    +
  • Bump .NET SDK to 5.0.4 (Internal 14775)
  • +
  • Disable running markdown link verification in release build CI (#14971, #14974, #14975)
  • +
  • Use template that disables component governance for CI (#14938)
  • +
  • Declare which variable group is used for checking the blob in the release build (#14970)
  • +
  • Add suppress for nuget multi-feed warning (#14893)
  • +
  • Disable code signing validation where the file type is not supported (#14885)
  • +
  • Install wget on CentOS 7 docker image (#14857)
  • +
  • Fix install-dotnet download (#14856)
  • +
  • Make universal Deb package based on deb package spec (#14681)
  • +
  • Fix release build to upload global tool packages to artifacts (#14620)
  • +
  • Update ini component version in test package.json (#14454)
  • +
  • Add manual release automation steps and improve changelog script (#14445)
  • +
  • Update markdown test packages with security fixes (#14145)
  • +
  • Fix a typo in the Get-ChangeLog function (#14129)
  • +
  • Disable global tool copy to unblock release
  • +
+ +
+ +[7.1.3]: https://github.com/PowerShell/PowerShell/compare/v7.1.2...v7.1.3 + +## [7.1.2] - 2021-02-11 + +### Build and Packaging Improvements + +
+ + +Bump .NET SDK to version 5.0.103 + + +
    +
  • Fix third party signing for files in sub-folders (#14751)
  • +
  • Bump .NET SDK to version 5.0.103 (Internal 14459)
  • +
  • Publish the global tool package for stable release
  • +
+ +
+ +[7.1.2]: https://github.com/PowerShell/PowerShell/compare/v7.1.1...v7.1.2 + +## [7.1.1] - 2021-01-14 + +### General Cmdlet Updates and Fixes + +- Avoid an exception if file system does not support reparse points (#13634) (Thanks @iSazonov!) +- Make AppLocker Enforce mode take precedence over UMCI Audit mode (#14353) + +### Code Cleanup + +- Fix syntax error in Windows packaging script (#14377) + +### Build and Packaging Improvements + +
+ +
    +
  • Use one feed in each nuget.config in official builds (#14363)
  • +
  • Fix path signed RPMs are uploaded from in release build (#14424)
  • +
  • Fix issue with unsigned build (#14367)
  • +
  • Move macOS and NuGet packages to ESRP signing (#14324)
  • +
  • Move Windows packages signing to use ESRP (#14060)
  • +
  • Move Linux packages to ESRP signing (#14210)
  • +
  • Migrate 3rd party signing to ESRP (#14010)
  • +
  • Don't do a shallow checkout (#13992)
  • +
  • Move to ESRP signing for Windows files (#13988)
  • +
  • Add checkout step to release build templates (#13840)
  • +
+ +
+ +[7.1.1]: https://github.com/PowerShell/PowerShell/compare/v7.1.0...v7.1.1 + +## [7.1.0] - 2020-11-11 + +### Engine Updates and Fixes + +- Fix a logic bug in `MapSecurityZone` (#13921) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Update `pwsh -?` output to match docs (#13748) + +### Tests + +- `markdownlint` security updates (#13730) + +### Build and Packaging Improvements + +
+ +
    +
  • Fixes to release pipeline for GA release (Internal 13410)
  • +
  • Add validation and dependencies for Ubuntu 20.04 distribution to packaging script (#13993)
  • +
  • Change PkgES Lab to unblock build (Internal 13376)
  • +
  • Add .NET install workaround for RTM (#13991)
  • +
  • Bump Microsoft.PowerShell.Native version from 7.1.0-rc.2 to 7.1.0 (#13976)
  • +
  • Bump 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)
  • +
+ +
+ +[7.1.0]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-rc.2...v7.1.0 + +## [7.1.0-rc.2] - 2020-10-20 + +### Engine Updates and Fixes + +- Rename `Get-Subsystem` to `Get-PSSubsystem` and fix two related minor issues (#13765) +- Add missing `PSToken` token table entries to fix the `PSParser` API (#13779) +- Add additional PowerShell modules to the tracked modules list (#12183) +- Fix blocking wait when starting file associated with a Windows application (#13750) +- Revert `PSNativePSPathResolution` to being an experimental feature (#13734) + +### General Cmdlet Updates and Fixes + +- Emit warning if `ConvertTo-Json` exceeds `-Depth` value (#13692) + +### Build and Packaging Improvements + +- Change Linux package script call to publish to the production repository in release builds (#13714) +- Update `PSReadLine` version to `2.1.0-rc1` (#13777) +- Move PowerShell build to dotnet `5.0-RC.2` (#13780) +- Bump `Microsoft.PowerShell.Native` to `7.1.0-rc.2` (#13794) + +[7.1.0-rc.2]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-rc.1...v7.1.0-rc.2 + +## [7.1.0-rc.1] - 2020-09-29 + +### Engine Updates and Fixes + +- Make fixes to `ComInterop` code as suggested by .NET team (#13533) + +### General Cmdlet Updates and Fixes + +- Fix case where exception message contains just ``"`n"`` on Windows (#13684) +- Recognize `CONOUT$` and `CONIN$` as reserved device names (#13508) (Thanks @davidreis97!) +- Fix `ConciseView` for interactive advanced function when writing error (#13623) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @soccypowa

+ +
+ +
    +
  • Simplify logical negation (#13555) (Thanks @xtqqczze!)
  • +
  • Fixed the indentation of the help content for -nologo (#13557) (Thanks @soccypowa!)
  • +
+ +
+ +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@heaths

+ +
+ +
    +
  • Bump NJsonSchema from 10.1.24 to 10.1.26 (#13586)
  • +
  • Bump PowerShellGet from 2.2.4 to 2.2.5 (#13683)
  • +
  • Bump Microsoft.ApplicationInsights from 2.14.0 to 2.15.0 (#13639)
  • +
  • Update PowerShell to build against dotnet 5.0-RC.1 (#13643)
  • +
  • Write the InstallLocation to fixed registry key (#13576) (Thanks @heaths!)
  • +
+ +
+ +### Documentation and Help Content + +- Update `README` and `metadata.json` for `7.1.0-preview.7` release (#13565) + +[7.1.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.7...v7.1.0-rc.1 + +## [7.1.0-preview.7] - 2020-09-08 + +### Breaking Changes + +- Fix `$?` to not be `$false` when native command writes to `stderr` (#13395) + +### Engine Updates and Fixes + +- Initial work of the subsystem plugin model (for minimal powershell) (#13186) +- Optimize `GetSystemLockdownPolicy` for non-lockdown scenarios (#13438) + +### General Cmdlet Updates and Fixes + +- Revert "Add the parameter `-Paged` to `Get-Help` to support paging (#13374)" (#13519) +- Add support for `TLS` 1.3 in Web cmdlets (#13409) (Thanks @iSazonov!) +- Add null check for `args` in `CommandLineParser` (#13451) (Thanks @iSazonov!) +- Process reparse points for Microsoft Store applications (#13481) (Thanks @iSazonov!) +- Move `PSNullConditionalOperators` feature out of experimental (#13529) +- Move `PSNativePSPathResolution` feature out of Experimental (#13522) +- Use field if property does not exist for `ObRoot` when using PowerShell Direct to container (#13375) (Thanks @hemisphera!) +- Suppress `UTF-7` obsolete warnings (#13484) +- Avoid multiple enumerations of an `IEnumerable` instance in `Compiler.cs` (#13491) +- Change `Add-Type -OutputType` to not support `ConsoleApplication` and `WindowsApplication` (#13440) +- Create warnings when `UTF-7` is specified as an encoding (#13430) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @tamasvajk

+ +
+ +
    +
  • Add single blank line after copyright header (#13486) (Thanks @xtqqczze!)
  • +
  • Use read-only auto-implemented properties (#13507) (Thanks @xtqqczze!)
  • +
  • Use boolean instead of bitwise operators on bool values (#13506) (Thanks @xtqqczze!)
  • +
  • Fix erroneous assert (#13495) (Thanks @tamasvajk!)
  • +
  • Cleanup: remove duplicate words in comments (#13539) (Thanks @xtqqczze!)
  • +
  • Reformat StringUtil (#13509) (Thanks @xtqqczze!)
  • +
  • Use uint instead of long for PDH constants (#13502) (Thanks @xtqqczze!)
  • +
  • Cleanup: Remove redundant empty lines (#13404) (Thanks @xtqqczze!)
  • +
  • Add StringUtil.Format overload to avoid unnecessary allocations (#13408) (Thanks @xtqqczze!)
  • +
  • Fix test hooks for CommandLineParameterParser (#13459)
  • +
  • Remove redundant delegate creation (#13441) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- vscode: Add `editorconfig` to recommended extensions (#13537) (Thanks @xtqqczze!) +- Remove the out-dated `ZapDisable` related code from `build.psm1` (#13350) (Thanks @jackerr3!) + +### Tests + +- Disable `WMF` download link validation test (#13479) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@yecril71pl

+ +
+ +
    +
  • Add Microsoft.NET.Test.Sdk dependency (Internal 12589)
  • +
  • Update .NET NuGet package version to 5.0.0-preview.8.20407.11 (Internal 12555)
  • +
  • Update to .NET 5 preview 8 (#13530)
  • +
  • Change stage dependency for docker release stage in release pipeline (#13512)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.7.0 to 16.7.1 (#13492)
  • +
  • Create the folder before copying the global tools (#13476)
  • +
  • A few fixes to the release pipeline (#13473)
  • +
  • Bump Markdig.Signed from 0.20.0 to 0.21.1 (#13463)
  • +
  • Add a pre-check for git to build.psm1 (#13227) (Thanks @yecril71pl!)
  • +
+ +
+ +### Documentation and Help Content + +- Update `README` links and `metadata.json` for `7.1.0-preview.6` (#13437) + +[7.1.0-preview.7]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.6...v7.1.0-preview.7 + +## [7.1.0-preview.6] - 2020-08-17 + +### Breaking Changes + +- Rename `-FromUnixTime` to `-UnixTimeSeconds` on `Get-Date` to allow Unix time input (#13084) (Thanks @aetos382!) +- Make `$ErrorActionPreference` not affect `stderr` output of native commands (#13361) +- Allow explicitly specified named parameter to supersede the same one from hashtable splatting (#13162) + +### Engine Updates and Fixes + +- Refactor command line parser to do early parsing (#11482) (Thanks @iSazonov!) +- Add support for some .NET intrinsic type converters (#12580) (Thanks @iSazonov!) +- Refresh and enable the `ComInterop` code in PowerShell (#13304) + +### Experimental Features + +- Add `-Runspace` parameter to all `*-PSBreakpoint` cmdlets (#10492) (Thanks @KirkMunro!) + +### General Cmdlet Updates and Fixes + +- Fix error message from new symbolic link missing target (#13085) (Thanks @yecril71pl!) +- Make the parameter `args` non-nullable in the public `ConsoleHost` APIs (#13429) +- Add missing dispose for `CancellationTokenSource` (#13420) (Thanks @Youssef1313!) +- Add the parameter `-Paged` to `Get-Help` to support paging (#13374) +- Fix `Get-Help` not properly displaying if parameter supports wildcards (#13353) (Thanks @ThomasNieto!) +- Update `pwsh` help for `-InputFormat` parameter (#13355) (Thanks @sethvs!) +- Declare MIT license for files copied from Roslyn (#13305) (Thanks @xtqqczze!) +- Improve `BigInteger` casting behaviors (#12629) (Thanks @vexx32!) +- Fix `Get-Acl -LiteralPath "HKLM:Software\Classes\*"` behavior (#13107) (Thanks @Shriram0908!) +- Add `DefaultVisit` method to the visitor interface and class (#13258) +- Fix conflicting shorthand switch `-s` (STA) for `pwsh` (#13262) (Thanks @iSazonov!) +- Change `Read-Host -MaskInput` to use existing `SecureString` path, but return as plain text (#13256) +- Remove `ComEnumerator` as COM objects using `IEnumerator` is now supported in .NET 5.0 (#13259) +- Use temporary personal path at Runspace startup when the 'HOME' environment variable is not defined (#13239) +- Fix `Invoke-Command` to detect recursive call of the same history entry (#13197) +- Change `pwsh` executable `-inputformat` switch prefix `-in` to `-inp` to fix conflict with `-interactive` (#13205) (Thanks @iSazonov!) +- Handle WSL filesystem path when analyze security zone of a file (#13120) +- Make other switches mandatory in `Split-Path` (#13150) (Thanks @kvprasoon!) +- New Fluent Design icon for PowerShell 7 (#13100) (Thanks @sarthakmalik!) +- Fix `Move-Item` to support cross-mount moves on Unix (#13044) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @yecril71pl, @ThomasNieto, @dgoldman-msft

+ +
+ +
    +
  • Use null check with pattern-matching instead of object.ReferenceEquals (#13065) (Thanks @xtqqczze!)
  • +
  • Fix comparison of value type object to null (#13285) (Thanks @xtqqczze!)
  • +
  • Use is operator instead of as operator (#13287) (Thanks @xtqqczze!)
  • +
  • Change SwitchParameter fields to properties (#13291) (Thanks @xtqqczze!)
  • +
  • Change "operable" to "executable" (#13281) (Thanks @yecril71pl!)
  • +
  • Remove AssemblyInfo property from list views (#13331) (Thanks @ThomasNieto!)
  • +
  • Use is not syntax where appropriate and remove unnecessary parentheses (#13323) (Thanks @xtqqczze!)
  • +
  • Remove unreachable code in CustomShellCommands.cs (#13316) (Thanks @xtqqczze!)
  • +
  • Add copyright header to .editorconfig and update files (#13306) (Thanks @xtqqczze!)
  • +
  • Fix typo in Out-File.cs and Out-Printer.cs (#13298) (Thanks @dgoldman-msft!)
  • +
  • Fix SA1026CodeMustNotContainSpaceAfterNewKeywordInImplicitlyTypedArrayAllocation (#13249) (Thanks @xtqqczze!)
  • +
  • Remove usage of do statement to create an infinite loop (#13137) (Thanks @xtqqczze!)
  • +
  • Use int instead of uint in places where it's more appropriate (#13141) (Thanks @xtqqczze!)
  • +
  • Use int instead of long to avoid Interlocked.Read (#13069) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Fix `dotnet` install errors (#13387) +- Increase the timeout of Windows daily build to 90 minutes (#13354) +- Update the `dependabot` configuration to version 2 (#13230) (Thanks @RDIL!) +- Fix `Test-XUnitTestResults` function (#13270) (Thanks @iSazonov!) +- Update `.devcontainer` to use nightly docker SDK images (#13128) + +### Tests + +- Mark `Test-Connection -TraceRoute` tests as pending (#13310) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @iSazonov, @77, @WorrenB

+ +
+ +
    +
  • Update README.md and metadata.json for next release (#13059)
  • +
  • Create release pipeline as a yaml pipeline (#13394)
  • +
  • Update infrastructure to consume private builds from .NET (#13427)
  • +
  • Fix breaks in packages daily build due to macOS signing changes (#13421)
  • +
  • Sign individual files for macOS PKG (#13392)
  • +
  • Disable code sign validation on jobs that do not sign (#13389)
  • +
  • Bump PSReadLine from 2.0.2 to 2.0.4 (#13240)
  • +
  • Update build documentation for Visual Studio 2019 dependency (#13336) (Thanks @xtqqczze!)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 3.6.0 to 3.7.0 (#13360)
  • +
  • Bump Microsoft.NET.Test.Sdk from 16.6.1 to 16.7.0 (#13364)
  • +
  • Bump xunit.runner.visualstudio from 2.4.2 to 2.4.3 (#13343)
  • +
  • Use Authenticode certificate for MSIX signing (#13330)
  • +
  • Add default help content to the assets folder (#13257)
  • +
  • Update .NET SDK version from 5.0.100-preview.7.20366.2 to 5.0.100-preview.7.20366.15 (#13200)
  • +
  • Set C# language version to preview/9.0 (#13090) (Thanks @iSazonov!)
  • +
  • Use pwsh for build and test of package in CI build (#13223)
  • +
  • Remove rcedit dependency, move daily ico dependency to props file (#13123)
  • +
  • Bump NJsonSchema from 10.1.23 to 10.1.24 (#13214)
  • +
  • Update .NET SDK version from 5.0.100-preview.7.20364.3 to 5.0.100-preview.7.20366.2 (#13192)
  • +
  • Add support for installing arm64 MSIX package. (#13043) (Thanks @77!)
  • +
  • Fix Azure file copy issues in release build (#13182)
  • +
  • Update .NET SDK version from 5.0.100-preview.7.20358.6 to 5.0.100-preview.7.20364.3 (#13155)
  • +
  • Fix Azure file copy break in Azure DevOps (#13173)
  • +
  • Bump Xunit.SkippableFact from 1.4.8 to 1.4.13 (#13143)
  • +
  • Add new chibi svg version of the avatar (#13160) (Thanks @WorrenB!)
  • +
  • Refactor MSI code to make it easier to add a WiX exe installer (#13139)
  • +
  • Disable ReadyToRun for debug build (#13144) (Thanks @iSazonov!)
  • +
  • Add new chibi version of the avatar (#13140)
  • +
  • Update .NET SDK version from 5.0.100-preview.7.20356.2 to 5.0.100-preview.7.20358.6 (#13134) (Thanks @github-actions[bot]!)
  • +
  • Update .NET SDK version from 5.0.100-preview.6.20318.15 to 5.0.100-preview.7.20356.2 (#13125) (Thanks @github-actions[bot]!)
  • +
+ +
+ +### Documentation and Help Content + +- Fix/clarify instructions for running Start-PSPester tests (#13373) +- Improve inline documentation for `VerbInfo` (#13265) (Thanks @yecril71pl!) +- Improve the wording of inline comments in the help system (#13274) (Thanks @yecril71pl!) +- Correct grammar in `README.md` and other docs (#13269) (Thanks @tasnimzotder!) +- Add "GitHub Actions Python builds" to `ADOPTERS.md` (#13228) (Thanks @brcrista!) +- Update change logs for `6.2.x` and `7.0.x` (#13194) +- Update `README.md` and `metadata.json` for the v7.0.3 release (#13187) + +[7.1.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.5...v7.1.0-preview.6 + +## [7.1.0-preview.5] - 2020-07-06 + +### Engine Updates and Fixes + +- Ensure assemblies listed in the module manifest `FileList` field are not loaded (#12968) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze

+ +
+ +
    +
  • Code performance fixes (#12956) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Add missing `.editorconfig` settings present in `dotnet/runtime` (#12871) (Thanks @xtqqczze!) + +### Tests + +- Add new test for `Format-Custom` to avoid data loss (#11393) (Thanks @iSazonov!) + +### Build and Packaging Improvements + +
+ + +

Fixed upgrade code in MSI package.

+ +
+
    +
  • Change log for v7.1.0-preview.5 (Internal 11880)
  • +
  • Fix Path for the Preview MSI (#13070)
  • +
  • Correct stable and preview upgrade codes for MSI (#13036)
  • +
  • Changelog for `v7.1.0-preview.4` (Internal 11841)
  • +
  • Fix NuGet package compliance issues (#13045)
  • +
  • Bump xunit.runner.visualstudio from 2.4.1 to 2.4.2 (#12874)
  • +
  • Bump NJsonSchema from `10.1.21` to `10.1.23` (#13032) (#13022)
  • +
+ +
+ +### Documentation and Help Content + +- Fix links for MSI packages to point to `7.1.0-preview.3` (#13056) +- Add update `packages.microsoft.com` step to distribution request template. (#13008) +- Update `windows-core.md` (#13053) (Thanks @xtqqczze!) +- Add `@rjmholt` to maintainers list (#13033) +- Update docs for `v7.1.0-preview.4` release (#13028) + +## [7.1.0-preview.4] - 2020-06-25 + +### Breaking Changes + +- Make the switch parameter `-Qualifier` not positional for `Split-Path` (#12960) (Thanks @yecril71pl!) +- Resolve the working directory as literal path for `Start-Process` when it's not specified (#11946) (Thanks @NoMoreFood!) +- Make `-OutFile` parameter in web cmdlets to work like `-LiteralPath` (#11701) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Ensure null-coalescing LHS is evaluated only once (#12667) +- Fix path handling bug in `PSTask` (#12554) (Thanks @IISResetMe!) +- Remove extra line before formatting group (#12163) (Thanks @iSazonov!) +- Make module formatting not generate error with strict mode (#11943) +- Adding more ETW logs to WSMan plugin (#12798) (Thanks @krishnayalavarthi!) +- Restrict loading of `amsi.dll` to `system32` folder (#12730) + +### General Cmdlet Updates and Fixes + +- Fix `NullReferenceException` in `CommandSearcher.GetNextCmdlet` (#12659) (Thanks @powercode!) +- Prevent `NullReferenceException` in Unix computer cmdlets with test hooks active (#12651) (Thanks @vexx32!) +- Fix issue in `Select-Object` where `Hashtable` members (e.g. `Keys`) cannot be used with `-Property` or `-ExpandProperty` (#11097) (Thanks @vexx32!) +- Fix conflicting shorthand switch `-w` for pwsh (#12945) +- Rename the `CimCmdlet` resource file (#12955) (Thanks @iSazonov!) +- Remove use of `Test-Path` in `ConciseView` (#12778) +- Flag `default` switch statement condition clause as keyword (#10487) (Thanks @msftrncs!) +- Add parameter `SchemaFile` to `Test-Json` cmdlet (#11934) (Thanks @beatcracker!) +- Bring back Certificate provider parameters (#10622) (Thanks @iSazonov!) +- Fix `New-Item` to create symbolic link to relative path target (#12797) (Thanks @iSazonov!) +- Add `CommandLine` property to Process (#12288) (Thanks @iSazonov!) +- Adds `-MaskInput` parameter to `Read-Host` (#10908) (Thanks @davinci26!) +- Change `CimCmdlets` to use `AliasAttribute` (#12617) (Thanks @thlac!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @sethvs, @romero126, @kvprasoon, @powercode

+ +
+ +
    +
  • Use nameof operator (#12716) (Thanks @xtqqczze!)
  • +
  • Fix comments in Mshexpression.cs (#12711) (Thanks @sethvs!)
  • +
  • Formatting: remove duplicate semicolons (#12666) (Thanks @xtqqczze!)
  • +
  • Replace SortedList with Generic.SortedList<TKey,TValue> (#12954) (Thanks @xtqqczze!)
  • +
  • Use HashSet instead of Hashtable with null values (#12958) (Thanks @xtqqczze!)
  • +
  • Rename CopyItem.Tests.ps1 to Copy-Item.Tests.ps1 to match other tests (#10701) (Thanks @romero126!)
  • +
  • Fix RCS1114: Remove redundant delegate creation (#12917) (Thanks @xtqqczze!)
  • +
  • Code redundancy fixes (#12916) (Thanks @xtqqczze!)
  • +
  • Update the PowerShell modules to use the new Help URI (#12686)
  • +
  • Reorder modifiers according to preferred order (#12864) (Thanks @xtqqczze!)
  • +
  • Expand numberOfPowershellRefAssemblies list capacity (#12840) (Thanks @xtqqczze!)
  • +
  • Add readonly modifier to internal static members (#11777) (Thanks @xtqqczze!)
  • +
  • cleanup: Use coalesce expression (#12829) (Thanks @xtqqczze!)
  • +
  • Add missing assessibility modifiers (#12820) (Thanks @xtqqczze!)
  • +
  • Use t_ naming convention for ThreadStatic members (#12826) (Thanks @xtqqczze!)
  • +
  • Formatting: Add empty line between declarations (#12824) (Thanks @xtqqczze!)
  • +
  • Clarify defaultRefAssemblies list capacity in AddType.cs (#12520) (Thanks @xtqqczze!)
  • +
  • Fixing "Double "period" (..) in message for System.InvalidOperationException" (#12758) (Thanks @kvprasoon!)
  • +
  • Rethrow to preserve stack details for better maintainability (#12723) (Thanks @xtqqczze!)
  • +
  • Delete license.rtf (#12738) (Thanks @xtqqczze!)
  • +
  • Nullable annotations for CommandSearcher (#12733) (Thanks @powercode!)
  • +
  • Redundancy: Remove 'partial' modifier from type with a single part (#12725) (Thanks @xtqqczze!)
  • +
  • Remove phrase 'All rights reserved' from Microsoft copyright statements (#12722) (Thanks @xtqqczze!)
  • +
  • IDictionary -> IDictionary<string, FunctionInfo> for FunctionTable (#12658) (Thanks @powercode!)
  • +
+ +
+ +### Tools + +- Use correct isError parameter with Write-Log (#12989) +- Disable `NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter` rule in `StyleCop` (#12855) (Thanks @xtqqczze!) +- Add @TylerLeonhardt to PowerShell team list to correct changelog generation (#12927) +- Enable the upload of `ETW` traces to `CLR CAP` in Windows daily build (#12890) +- Prevent GitHub workflow for daily dotnet build updates from running in forks (#12763) (Thanks @bergmeister!) +- Add GitHub action for PR creation and `Wix` file generation logic (#12748) + +### Tests + +- Remove duplicate tests from `Measure-Object.Tests.ps1` (#12683) (Thanks @sethvs!) +- Fix tests to not write errors to console (#13010) +- Make sure tabcompletion tests run (#12981) +- Remove dependency on DNS for `Test-Connection` tests on macOS (#12943) +- Restore `markdownlint` tests (#12549) (Thanks @xtqqczze!) +- Wrap tests in pester blocks (#12700) (Thanks @xtqqczze!) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@iSazonov, @kvprasoon, @Saancreed, @heaths, @xtqqczze

+ +
+ +
    +
  • Update Distribution_Request.md
  • +
  • Bump NJsonSchema from 10.1.15 to 10.1.16 (#12685)
  • +
  • Disable uploading Symbols package (#12687)
  • +
  • Update .NET SDK version from 5.0.100-preview.5.20279.10 to 5.0.100-preview.6.20318.15 (#13018)
  • +
  • Remove component ref when re-generating the wix file (#13019)
  • +
  • Make sure icons are added to MSI staging folder (#12983)
  • +
  • Update DotnetRutimeMetadata.json to point to preview 6 (#12972)
  • +
  • Bump PSReadLine from 2.0.1 to 2.0.2 (#12909)
  • +
  • Bump NJsonSchema from 10.1.18 to 10.1.21 (#12944)
  • +
  • Check if Azure Blob exists before overwriting (#12921)
  • +
  • Enable skipped tests (#12894) (Thanks @iSazonov!)
  • +
  • Fix break in package build by pinning ffi version to 1.12 (#12889)
  • +
  • Upgrade APIScan version (#12876)
  • +
  • Make contributors unique in Release notes (#12878) (Thanks @kvprasoon!)
  • +
  • Update Linux daily CI to run in a single agent & collect traces (#12866)
  • +
  • Update .NET SDK version from 5.0.100-preview.5.20278.13 to 5.0.100-preview.5.20279.10 (#12844) (Thanks @github-actions[bot]!)
  • +
  • Sign the MSIX files for the store (#12582)
  • +
  • Update the CI builds (#12830)
  • +
  • Update .NET SDK version from 5.0.100-preview.5.20272.6 to 5.0.100-preview.5.20278.13 (#12772) (Thanks @github-actions[bot]!)
  • +
  • Allow use of build module on unknown Linux distros (#11146) (Thanks @Saancreed!)
  • +
  • Fix MSI upgrade and shortcut issues (#12792) (Thanks @heaths!)
  • +
  • Bump NJsonSchema from 10.1.17 to 10.1.18 (#12812)
  • +
  • Update .NET SDK version from 5.0.100-preview.5.20269.29 to 5.0.100-preview.5.20272.6 (#12759) (Thanks @github-actions[bot]!)
  • +
  • Bump NJsonSchema from 10.1.16 to 10.1.17 (#12761)
  • +
  • Update to dotnet SDK 5.0.0-preview.5.20268.9 (#12740)
  • +
  • Remove assets\license.rtf (#12721) (Thanks @xtqqczze!)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 3.5.0 to 3.6.0 (#12731)
  • +
+ +
+ +### Documentation and Help Content + +- Update `README` and `metadata` files for next release (#12717) +- Update `README.md` removing experimental status of `Arm` builds, but `Win-Arm64` is still preview for Stable release. (#12707) +- Add link to Github compare in changelog (#12713) (Thanks @xtqqczze!) +- Added missing changelog for v7.1.0-preview.2 (#12665) +- Update required Visual Studio version in build docs (#12628) (Thanks @xtqqczze!) +- minor update to Distribution_Request.md (#12705) (Thanks @kilasuit!) +- Update docs.microsoft.com links (#12653) (Thanks @xtqqczze!) +- Update change log for `6.2.5` release (#12670) +- Update `README.md` and `metadata.json` for next release (#12668) +- Merge 7.0.1 change log (#12669) +- Remove markdown unused definitions (#12656) (Thanks @xtqqczze!) +- Add HoloLens to list of PowerShell adopters (#12940) (Thanks @reynoldsbd!) +- Update `README.md` and `metadata.json` for next releases (#12939) +- Fix broken link in `README.md` (#12887) (Thanks @xtqqczze!) +- Minor typo corrections in Distribution Request Issue Templates (#12744) (Thanks @corbob!) +- Correct 'review-for-comments' in `Governance.md` (#11035) (Thanks @MarvTheRobot!) +- Fix markdown ordered lists (#12657) (Thanks @xtqqczze!) +- Fix broken `docs.microsoft.com` link (#12776) (Thanks @xtqqczze!) +- Replace link to Slack with link to PowerShell Virtual User Group (#12786) (Thanks @xtqqczze!) +- Update `LICENSE.txt` so that it's recognized as MIT (#12729) + +## [7.1.0-preview.3] - 2020-05-14 + +### Breaking Changes + +- Fix string parameter binding for `BigInteger` numeric literals (#11634) (Thanks @vexx32!) + +### Engine Updates and Fixes + +- Set correct `PSProvider` full name at module load time (#11813) (Thanks @iSazonov!) + +### Experimental Features + +- Support passing `PSPath` to native commands (#12386) + +### General Cmdlet Updates and Fixes + +- Fix incorrect index in format string in ParameterBinderBase (#12630) (Thanks @powercode!) +- Copy the `CommandInfo` property in `Command.Clone()` (#12301) (Thanks @TylerLeonhardt!) +- Apply `-IncludeEqual` in `Compa-Object` when `-ExcludeDifferent` is specified (#12317) (Thanks @davidseibel!) +- Change `Get-FileHash` to close file handles before writing output (#12474) (Thanks @HumanEquivalentUnit!) +- Fix inconsistent exception message in `-replace` operator (#12388) (Thanks @jackdcasey!) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@xtqqczze, @RDIL, @powercode, @xtqqczze, @xtqqczze

+ +
+ +
    +
  • Replace Unicode non-breaking space character with space (#12576) (Thanks @xtqqczze!)
  • +
  • Remove unused New-DockerTestBuild.ps1 (#12610) (Thanks @RDIL!)
  • +
  • Annotate Assert methods for better code analysis (#12618) (Thanks @powercode!)
  • +
  • Use correct casing for cmdlet names and parameters in *.ps1 files throughout the codebase (#12584) (Thanks @xtqqczze!)
  • +
  • Document why PackageVersion is used in PowerShell.Common.props (#12523) (Thanks @xtqqczze!)
  • +
+ +
+ +### Tools + +- Update `@PoshChan` config to include `SSH` (#12526) (Thanks @vexx32!) +- Update log message in `Start-PSBootstrap` (#12573) (Thanks @xtqqczze!) +- Add the `.NET SDK` installation path to the current process path in `tools/UpdateDotnetRuntime.ps1` (#12525) + +### Tests + +- Make CIM tab completion test case insensitive (#12636) +- Mark ping tests as Pending due to stability issues in macOS (#12504) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@jcotton42, @iSazonov, @iSazonov, @iSazonov

+ +
+ +
    +
  • Update build to use the new .NET SDK 5.0.100-preview.4.20258.7 (#12637)
  • +
  • Bump NJsonSchema from 10.1.14 to 10.1.15 (#12608)
  • +
  • Bump NJsonSchema from 10.1.13 to 10.1.14 (#12598)
  • +
  • Bump NJsonSchema from 10.1.12 to 10.1.13 (#12583)
  • +
  • Update the build to sign any unsigned files as 3rd party Dlls (#12581)
  • +
  • Update .NET SDK to 5.0.100-preview.4.20229.10 (#12538)
  • +
  • Add ability to Install-Dotnet to specify directory (#12469)
  • +
  • Allow / in relative paths for using module (#7424) (#12492) (Thanks @jcotton42!)
  • +
  • Update dotnet metadata for next channel for automated updates (#12502)
  • +
  • Bump .NET to 5.0.0-preview.4 (#12507)
  • +
  • Bump Microsoft.ApplicationInsights from 2.13.1 to 2.14.0 (#12479)
  • +
  • Bump PackageManagement from 1.4.6 to 1.4.7 in /src/Modules (#12506)
  • +
  • Bump Xunit.SkippableFact from 1.3.12 to 1.4.8 (#12480)
  • +
  • Fix quotes to allow variable expansion (#12512)
  • +
  • Use new TargetFramework as net5.0 in packaging scripts (#12503) (Thanks @iSazonov!)
  • +
  • Use new value for TargetFramework as net5.0 instead of netcoreapp5.0 (#12486) (Thanks @iSazonov!)
  • +
  • Disable PublishReadyToRun for framework dependent packages (#12450)
  • +
  • Add dependabot rules to ignore updates from .NET (#12466)
  • +
  • Update README.md and metadata.json for upcoming release (#12441)
  • +
  • Turn on ReadyToRun (#12361) (Thanks @iSazonov!)
  • +
  • Add summary to compressed sections of change log (#12429)
  • +
+ +
+ +### Documentation and Help Content + +- Add link to life cycle doc to distribution request template (#12638) +- Update TFM reference in build docs (#12514) (Thanks @xtqqczze!) +- Fix broken link for blogs in documents (#12471) + +## [7.1.0-preview.2] - 2020-04-23 + +### Breaking Changes + +- On Windows, `Start-Process` creates a process environment with + all the environment variables from current session, + using `-UseNewEnvironment` creates a new default process environment (#10830) (Thanks @iSazonov!) +- Do not wrap return result to `PSObject` when converting ScriptBlock to delegate (#10619) + +### Engine Updates and Fixes + +- Allow case insensitive paths for determining `PSModulePath` (#12192) +- Add PowerShell version 7.0 to compatible version list (#12184) +- Discover assemblies loaded by `Assembly.Load(byte[])` and `Assembly.LoadFile` (#12203) + +### General Cmdlet Updates and Fixes + +- Fix `WinCompat` module loading to treat PowerShell 7 modules with higher priority (#12269) +- Implement `ForEach-Object -Parallel` runspace reuse (#12122) +- Fix `Get-Service` to not modify collection while enumerating it (#11851) (Thanks @NextTurn!) +- Clean up the IPC named pipe on PowerShell exit (#12187) +- Fix `` detection regex in web cmdlets (#12099) (Thanks @vexx32!) +- Allow shorter signed hex literals with appropriate type suffixes (#11844) (Thanks @vexx32!) +- Update `UseNewEnvironment` parameter behavior of `Start-Process` cmdlet on Windows (#10830) (Thanks @iSazonov!) +- Add `-Shuffle` switch to `Get-Random` command (#11093) (Thanks @eugenesmlv!) +- Make `GetWindowsPowerShellModulePath` compatible with multiple PS installations (#12280) +- Fix `Start-Job` to work on systems that don't have Windows PowerShell registered as default shell (#12296) +- Specifying an alias and `-Syntax` to `Get-Command` returns the aliased commands syntax (#10784) (Thanks @ChrisLGardner!) +- Make CSV cmdlets work when using `-AsNeeded` and there is an incomplete row (#12281) (Thanks @iSazonov!) +- In local invocations, do not require `-PowerShellVersion 5.1` for `Get-FormatData` in order to see all format data. (#11270) (Thanks @mklement0!) +- Added Support For Big Endian `UTF-32` (#11947) (Thanks @NoMoreFood!) +- Fix possible race that leaks PowerShell object dispose in `ForEach-Object -Parallel` (#12227) +- Add `-FromUnixTime` to `Get-Date` to allow Unix time input (#12179) (Thanks @jackdcasey!) +- Change default progress foreground and background colors to provide improved contrast (#11455) (Thanks @rkeithhill!) +- Fix `foreach -parallel` when current drive is not available (#12197) +- Do not wrap return result to `PSObject` when converting `ScriptBlock` to `delegate` (#10619) +- Don't write DNS resolution errors on `Test-Connection -Quiet` (#12204) (Thanks @vexx32!) +- Use dedicated threads to read the redirected output and error streams from the child process for out-of-proc jobs (#11713) + +### Code Cleanup + +
+ + + +

We thank the following contributors!

+

@ShaydeNofziger, @RDIL

+ +
+ +
    +
  • Fix erroneous comment in tokenizer.cs (#12206) (Thanks @ShaydeNofziger!)
  • +
  • Fix terms checker issues (#12189)
  • +
  • Update copyright notice to latest guidance (#12190)
  • +
  • CodeFactor cleanup (#12251) (Thanks @RDIL!)
  • +
+ +
+ +### Tools + +- Update .NET dependency update script to include test `csproj` files (#12372) +- Scripts to update to .NET prerelease version (#12284) + +### Tests + +- Pin major Pester version to 4 to prevent breaking changes caused by upcoming release of v5 (#12262) (Thanks @bergmeister!) + +### Build and Packaging Improvements + +
+ + + +

We thank the following contributors!

+

@rkitover, @bergmeister

+ +
+ +
    +
  • Add the nuget.config from root to the temporary build folder (#12394)
  • +
  • Bump System.IO.Packaging (#12365)
  • +
  • Bump Markdig.Signed from 0.18.3 to 0.20.0 (#12379)
  • +
  • Bump to .NET 5 Preview 3 pre-release (#12353)
  • +
  • Bump PowerShellGet from 2.2.3 to 2.2.4 (#12342)
  • +
  • Linux: Initial support for Gentoo installations. (#11429) (Thanks @rkitover!)
  • +
  • Upgrade to .NET 5 Preview 2 (#12250) (Thanks @bergmeister!)
  • +
  • Fix the Sync PSGalleryModules to Artifacts build (#12277)
  • +
  • Bump PSReadLine from 2.0.0 to 2.0.1 (#12243)
  • +
  • Bump NJsonSchema from 10.1.11 to 10.1.12 (#12230)
  • +
  • Update change log generation script to support collapsible sections (#12214)
  • +
+ +
+ +### Documentation and Help Content + +- Add documentation for `WebResponseObject` and `BasicHtmlWebResponseObject` properties (#11876) (Thanks @kevinoid!) +- Add Windows 10 IoT Core reference in `Adopters.md` (#12266) (Thanks @parameshbabu!) +- Update `README.md` and `metadata.json` for `7.1.0-preview.1` (#12211) + +## [7.1.0-preview.1] - 2020-03-26 + +### Breaking Changes + +- Use invariant culture string conversion for `-replace` operator (#10954) (Thanks @iSazonov!) + +### Engine Updates and Fixes + +- Revert the PRs that made `DBNull.Value` and `NullString.Value` treated as `$null` (#11648) + +### Experimental Features + +- Use invariant culture string conversion for `-replace` operator (#10954) (Thanks @iSazonov!) + +### General Cmdlet Updates and Fixes + +- Fix an operator preference order issue in binder code (#12075) (Thanks @DamirAinullin!) +- Fix `NullReferenceException` when binding common parameters of type `ActionPreference` (#12124) +- Fix default formatting for deserialized `MatchInfo` (#11728) (Thanks @iSazonov!) +- Use asynchronous streams in `Invoke-RestMethod` (#11095) (Thanks @iSazonov!) +- Address UTF-8 Detection In `Get-Content -Tail` (#11899) (Thanks @NoMoreFood!) +- Handle the `IOException` in `Get-FileHash` (#11944) (Thanks @iSazonov!) +- Change `PowerShell Core` to `PowerShell` in a resource string (#11928) (Thanks @alexandair!) +- Bring back `MainWindowTitle` in `PSHostProcessInfo` (#11885) (Thanks @iSazonov!) +- Miscellaneous minor updates to Windows Compatibility (#11980) +- Fix `ConciseView` to split `PositionMessage` using `[Environment]::NewLine` (#12010) +- Remove network hop restriction for interactive sessions (#11920) +- Fix `NullReferenceException` in `SuspendStoppingPipeline()` and `RestoreStoppingPipeline()` (#11870) (Thanks @iSazonov!) +- Generate GUID for `FormatViewDefinition` `InstanceId` if not provided (#11896) +- Fix `ConciseView` where error message is wider than window width and doesn't have whitespace (#11880) +- Allow cross-platform `CAPI-compatible` remote key exchange (#11185) (Thanks @silijon!) +- Fix error message (#11862) (Thanks @NextTurn!) +- Fix `ConciseView` to handle case where there isn't a console to obtain the width (#11784) +- Update `CmsCommands` to use Store vs certificate provider (#11643) (Thanks @mikeTWC1984!) +- Enable `pwsh` to work on Windows systems where `mpr.dll` and STA is not available (#11748) +- Refactor and implement `Restart-Computer` for `Un*x` and macOS (#11319) +- Add an implementation of `Stop-Computer` for Linux and macOS (#11151) +- Fix `help` function to check if `less` is available before using (#11737) +- Update `PSPath` in `certificate_format_ps1.xml` (#11603) (Thanks @xtqqczze!) +- Change regular expression to match relation-types without quotes in Link header (#11711) (Thanks @Marusyk!) +- Fix error message during symbolic link deletion (#11331) +- Add custom `Selected.*` type to `PSCustomObject` in `Select-Object` only once (#11548) (Thanks @iSazonov!) +- Add `-AsUTC` to the `Get-Date` cmdlet (#11611) +- Fix grouping behavior with Boolean values in `Format-Hex` (#11587) (Thanks @vexx32!) +- Make `Test-Connection` always use the default synchronization context for sending ping requests (#11517) +- Correct startup error messages (#11473) (Thanks @iSazonov!) +- Ignore headers with null values in web cmdlets (#11424) (Thanks @iSazonov!) +- Re-add check for `Invoke-Command` job dispose. (#11388) +- Revert "Update formatter to not write newlines if content is empty (#11193)" (#11342) (Thanks @iSazonov!) +- Allow `CompleteInput` to return results from `ArgumentCompleter` when `AST` or Script has matching function definition (#10574) (Thanks @M1kep!) +- Update formatter to not write new lines if content is empty (#11193) + +### Code Cleanup + +
+ +
    +
  • Use span-based overloads (#11884) (Thanks @iSazonov!)
  • +
  • Use new string.Split() overloads (#11867) (Thanks @iSazonov!)
  • +
  • Remove unreachable DSC code (#12076) (Thanks @DamirAinullin!)
  • +
  • Remove old dead code from FullCLR (#11886) (Thanks @iSazonov!)
  • +
  • Use Dictionary.TryAdd() where possible (#11767) (Thanks @iSazonov!)
  • +
  • Use Environment.NewLine instead of hard-coded linefeed in ParseError.ToString (#11746)
  • +
  • Fix FileSystem provider error message (#11741) (Thanks @iSazonov!)
  • +
  • Reformat code according to EditorConfig rules (#11681) (Thanks @xtqqczze!)
  • +
  • Replace use of throw GetExceptionForHR with ThrowExceptionForHR (#11640) (Thanks @xtqqczze!)
  • +
  • Refactor delegate types to lambda expressions (#11690) (Thanks @xtqqczze!)
  • +
  • Remove Unicode BOM from text files (#11546) (Thanks @xtqqczze!)
  • +
  • Fix Typo in Get-ComputerInfo cmdlet description (#11321) (Thanks @doctordns!)
  • +
  • Fix typo in description for Get-ExperimentalFeature PSWindowsPowerShellCompatibility (#11282) (Thanks @alvarodelvalle!)
  • +
  • Cleanups in command discovery (#10815) (Thanks @iSazonov!)
  • +
  • Review CurrentCulture (#11044) (Thanks @iSazonov!)
  • +
+ +
+ +### Tools + +- Change recommended VS Code extension name from `ms-vscode.csharp` to `ms-dotnettools.csharp` (#12083) (Thanks @devlead!) +- Specify `csharp_preferred_modifier_order` in `EditorConfig` (#11775) (Thanks @xtqqczze!) +- Update `.editorconfig` (#11675) (Thanks @xtqqczze!) +- Enable `EditorConfig` support in `OmniSharp` (#11627) (Thanks @xtqqczze!) +- Specify charset in `.editorconfig` as `utf-8` (no BOM) (#11654) (Thanks @xtqqczze!) +- Configure the issue label bot (#11527) +- Avoid variable names that conflict with automatic variables (#11392) (Thanks @xtqqczze!) + +### Tests + +- Add empty `preview.md` file to fix broken link (#12041) +- Add helper functions for SSH remoting tests (#11955) +- Add new tests for `Get-ChildItem` for `FileSystemProvider` (#11602) (Thanks @iSazonov!) +- Ensure that types referenced by `PowerShellStandard` are present (#10634) +- Check state and report reason if it's not "opened" (#11574) +- Fixes for running tests on Raspbian (#11661) +- Unify pester test syntax for the arguments of `-BeOfType` (#11558) (Thanks @xtqqczze!) +- Correct casing for automatic variables (#11568) (Thanks @iSazonov!) +- Avoid variable names that conflict with automatic variables part 2 (#11559) (Thanks @xtqqczze!) +- Update pester syntax to v4 (#11544) (Thanks @xtqqczze!) +- Allow error 504 (Gateway Timeout) in `markdown-link` tests (#11439) (Thanks @xtqqczze!) +- Re-balance CI tests (#11420) (Thanks @iSazonov!) +- Include URL in the markdown-links test error message (#11438) (Thanks @xtqqczze!) +- Use CIM cmdlets instead of WMI cmdlets in tests (#11423) (Thanks @xtqqczze!) + +### Build and Packaging Improvements + +
+ +
    +
  • Put symbols in separate package (#12169)
  • +
  • Disable x86 PDB generation (#12167)
  • +
  • Bump NJsonSchema from 10.1.5 to 10.1.11 (#12050) (#12088) (#12166)
  • +
  • Create crossgen symbols for Windows x64 and x86 (#12157)
  • +
  • Move to .NET 5 preview.1 (#12140)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp from 3.4.0 to 3.5.0 (#12136)
  • +
  • Move to standard internal pool for building (#12119)
  • +
  • Fix package syncing to private Module Feed (#11841)
  • +
  • Add Ubuntu SSH remoting tests CI (#12033)
  • +
  • Bump Markdig.Signed from 0.18.1 to 0.18.3 (#12078)
  • +
  • Fix MSIX packaging to determine if a Preview release by inspecting the semantic version string (#11991)
  • +
  • Ignore last exit code in the build step as dotnet may return error when SDK is not installed (#11972)
  • +
  • Fix daily package build (#11882)
  • +
  • Fix package sorting for syncing to private Module Feed (#11838)
  • +
  • Set StrictMode version 3.0 (#11563) (Thanks @xtqqczze!)
  • +
  • Bump .devcontainer version to dotnet 3.1.101 (#11707) (Thanks @Jawz84!)
  • +
  • Move to version 3 of AzFileCopy (#11697)
  • +
  • Update README.md and metadata.json for next release (#11664)
  • +
  • Code Cleanup for environment data gathering in build.psm1 (#11572) (Thanks @xtqqczze!)
  • +
  • Update Debian Install Script To Support Debian 10 (#11540) (Thanks @RandomNoun7!)
  • +
  • Update ADOPTERS.md (#11261) (Thanks @edyoung!)
  • +
  • Change back to use powershell.exe in 'SetVersionVariables.yml' to unblock daily build (#11207)
  • +
  • Change to use pwsh to have consistent JSON conversion for DateTime (#11126)
  • +
+ +
+ +### Documentation and Help Content + +- Replace `VSCode` link in `CONTRIBUTING.md` (#11475) (Thanks @stevend811!) +- Remove the version number of PowerShell from LICENSE (#12019) +- Add the 7.0 change log link to `CHANGELOG/README.md` (#12062) (Thanks @LabhanshAgrawal!) +- Improvements to the contribution guide (#12086) (Thanks @ShaydeNofziger!) +- Update the doc about debugging dotnet core in VSCode (#11969) +- Update `README.md` and `metadata.json` for the next release (#11918) (#11992) +- Update `Adopters.md` to include info on Azure Pipelines and GitHub Actions (#11888) (Thanks @alepauly!) +- Add information about how Amazon AWS uses PowerShell. (#11365) (Thanks @bpayette!) +- Add link to .NET CLI version in build documentation (#11725) (Thanks @joeltankam!) +- Added info about `DeploymentScripts` in `ADOPTERS.md` (#11703) +- Update `CHANGELOG.md` for `6.2.4` release (#11699) +- Update `README.md` and `metadata.json` for next release (#11597) +- Update the breaking change definition (#11516) +- Adding System Frontier to the PowerShell Core adopters list `ADOPTERS.md` (#11480) (Thanks @OneScripter!) +- Update `ChangeLog`, `README.md` and `metadata.json` for `7.0.0-rc.1` release (#11363) +- Add `AzFunctions` to `ADOPTERS.md` (#11311) (Thanks @Francisco-Gamino!) +- Add `Universal Dashboard` to `ADOPTERS.md` (#11283) (Thanks @adamdriscoll!) +- Add `config.yml` for `ISSUE_TEMPLATE` so that Doc, Security, Support, and Windows PowerShell issues go to URLs (#11153) +- Add `Adopters.md` file (#11256) +- Update `Readme.md` for `preview.6` release (#11108) +- Update `SUPPORT.md` (#11101) (Thanks @mklement0!) +- Update `README.md` (#11100) (Thanks @mklement0!) + +[7.1.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.4...v7.1.0-preview.5 +[7.1.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.3...v7.1.0-preview.4 +[7.1.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.2...v7.1.0-preview.3 +[7.1.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.1...v7.1.0-preview.2 +[7.1.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.6...v7.1.0-preview.1 + 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..eb506a7ddbc --- /dev/null +++ b/CHANGELOG/7.4.md @@ -0,0 +1,1665 @@ +# 7.4 Changelog + +## [7.4.15] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27229) +- Close pipe client handles after creating the child ssh process (#27139) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27146) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.420

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

Update .NET SDK to 8.0.419

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

Update .NET SDK to 8.0.415

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

Update .NET SDK to 8.0.413

+ +
+ +
    +
  • Add LinuxHost Network configuration to PowerShell Packages pipeline (#26003)
  • +
  • Update container images to use mcr.microsoft.com for Linux and Azure Linux (#25987)
  • +
  • Update SDK to 8.0.413 (#25993)
  • +
  • Make logical template name consistent between pipelines (#25992)
  • +
  • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25965)
  • +
+ +
+ +### Documentation and Help Content + +- Update third-party library versions to `8.0.19` for `ObjectPool`, Windows Compatibility, and `System.Drawing.Common` (#26001) + +[7.4.12]: https://github.com/PowerShell/PowerShell/compare/v7.4.11...v7.4.12 + +## [7.4.11] - 2025-06-17 + +### Engine Updates and Fixes + +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25568) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.411

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

Update .NET SDK to 8.0.408

+ +
+ +
    +
  • Update branch for release (#25518)
  • +
  • Move MSIXBundle to Packages and Release to GitHub (#25516)
  • +
  • Add CodeQL suppressions for PowerShell intended behavior (#25376)
  • +
  • Enhance path filters action to set outputs for all changes when not a PR (#25378)
  • +
  • Fix Merge Errors from #25401 and Internal 33077 (#25478)
  • +
  • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25476)
  • +
  • Fix Conditional Parameter to Skip NuGet Publish (#25475)
  • +
  • Use new variables template for vPack (#25474)
  • +
  • Add Windows Store Signing to MSIX bundle (#25472)
  • +
  • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25471)
  • +
  • Fix the expected path of .NET after using UseDotnet 2 task to install (#25470)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#25469)
  • +
  • Combine GitHub and Nuget Release Stage (#25473)
  • +
  • Make GitHub Workflows work in the internal mirror (#25409)
  • +
  • Add default .NET install path for SDK validation (#25339)
  • +
  • Update APIScan to use new symbols server (#25400)
  • +
  • Use GitHubReleaseTask (#25401)
  • +
  • Migrate MacOS Signing to OneBranch (#25412)
  • +
  • Remove call to NuGet (#25410)
  • +
  • Restore a script needed for build from the old release pipeline cleanup (#25201) (#25408)
  • +
  • Switch to ubuntu-latest for CI (#25406)
  • +
  • Update GitHub Actions to work in private GitHub repository (#25403)
  • +
  • Simplify PR Template (#25407)
  • +
  • Disable SBOM generation on set variables job in release build (#25341)
  • +
  • Update package pipeline windows image version (#25192)
  • +
+ +
+ +[7.4.10]: https://github.com/PowerShell/PowerShell/compare/v7.4.9...v7.4.10 + +## [7.4.9] + +### Notes + +_This release is internal only. It is not available for download._ + +### Tools + +- Check GH token availability for `Get-Changelog` (#25156) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.407

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

Update .NET SDK to 8.0.406

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

Update .NET SDK to 8.0.405

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

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..b4f173bcff6 --- /dev/null +++ b/CHANGELOG/7.5.md @@ -0,0 +1,951 @@ +# 7.5 Changelog + +## [7.5.6] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27220) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27166) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET SDK 9.0.313

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

Update to .NET SDK 9.0.312

+

We thank the following contributors!

+

@kasperk81, @RichardSlater

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

Update to .NET SDK 9.0.306

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

Update to .NET SDK 9.0.304

+ +
+ +
    +
  • Make logical template name consistent between pipelines (#25991)
  • +
  • Update container images to use mcr.microsoft.com for Linux and Azure Linux (#25986)
  • +
  • Add build to vPack Pipeline (#25975)
  • +
  • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25964)
  • +
  • Update branch for release (#25942)
  • +
+ +
+ +### Documentation and Help Content + +- Fix typo in CHANGELOG for script filename suggestion (#25963) + +[7.5.3]: https://github.com/PowerShell/PowerShell/compare/v7.5.2...v7.5.3 + +## [7.5.2] - 2025-06-24 + +### Engine Updates and Fixes + +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25357) + +### General Cmdlet Updates and Fixes + +- Set standard handles explicitly when starting a process with `-NoNewWindow` (#25324) +- Make inherited protected internal instance members accessible in class scope. (#25547) (Thanks @mawosoft!) +- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25330) +- Fix `PSMethodInvocationConstraints.GetHashCode` method (#25306) (Thanks @crazyjncsu!) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET SDK 9.0.301

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

Update to .NET SDK 9.0.203

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

Update .NET SDK to 9.0.102

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

Bump to .NET 9.0.100

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

Bump .NET SDK to 9.0.100-rc.1.24452.12

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

We thank the following contributors!

+

@xtqqczze, @eltociear

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

We thank the following contributors!

+

@bosesubham2011

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

We thank the following contributors!

+

@xtqqczze

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

Bump to .NET 9.0.0-preview.3

+

We thank the following contributors!

+

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

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

We thank the following contributors!

+

@eltociear

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

Bump to .NET 9 Preview 1

+

We thank the following contributors!

+

@gregsdennis

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

We thank the following contributors!

+

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

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

We thank the following contributors!

+

@alerickson, @Zhoneym, @0o001

+ +
+ +
    +
  • Bump .NET SDK to 8.0.101 (#21084)
  • +
  • Update the cgmanifest (#20083, #20436, #20523, #20560, #20627, #20764, #20906, #20933, #20955, #21047)
  • +
  • Update to the latest NOTICES file (#20074, #20161, #20385, #20453, #20576, #20590, #20880, #20905)
  • +
  • Bump StyleCop.Analyzers from 1.2.0-beta.507 to 1.2.0-beta.556 (#20953)
  • +
  • Bump xUnit to 2.6.6 (#21071)
  • +
  • Bump JsonSchema.Net to 5.5.0 (#21027)
  • +
  • Fix failures in GitHub action markdown-link-check (#20996)
  • +
  • Bump xunit.runner.visualstudio to 2.5.6 (#20966)
  • +
  • Bump github/codeql-action from 2 to 3 (#20927)
  • +
  • Bump Markdig.Signed to 0.34.0 (#20926)
  • +
  • Bump Microsoft.ApplicationInsights from 2.21.0 to 2.22.0 (#20888)
  • +
  • Bump Microsoft.NET.Test.Sdk to 17.8.0 (#20660)
  • +
  • Update apiscan.yml to have access to the AzDevOpsArtifacts variable group (#20671)
  • +
  • Set the ollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689)
  • +
  • Sign the global tool shim executable (#20794)
  • +
  • Bump actions/github-script from 6 to 7 (#20682)
  • +
  • Remove RHEL7 publishing to packages.microsoft.com as it's no longer supported (#20849)
  • +
  • Bump Microsoft.CodeAnalysis.CSharp to 4.8.0 (#20751)
  • +
  • Add internal nuget feed to compliance build (#20669)
  • +
  • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (#20659)
  • +
  • Fix release build by making the internal SDK parameter optional (#20658)
  • +
  • Update PSResourceGet version to 1.0.1 (#20652)
  • +
  • Make internal .NET SDK URL as a parameter for release builld (#20655)
  • +
  • Fix setting of variable to consume internal SDK source (#20644)
  • +
  • Bump Microsoft.Management.Infrastructure to v3.0.0 (#20642)
  • +
  • Bump Microsoft.PowerShell.Native to v7.4.0 (#20617)
  • +
  • Bump Microsoft.Security.Extensions from 1.2.0 to 1.3.0 (#20556)
  • +
  • Fix package version for .NET nuget packages (#20551)
  • +
  • Add SBOM for release pipeline (#20519)
  • +
  • Block any preview vPack release (#20243)
  • +
  • Only registry App Path for release package (#20478)
  • +
  • Increase timeout when publishing packages to pacakages.microsoft.com (#20470)
  • +
  • Fix alpine tar package name and do not crossgen alpine fxdependent package (#20459)
  • +
  • Bump PSReadLine from 2.2.6 to 2.3.4 (#20305)
  • +
  • Remove the ref folder before running compliance (#20373)
  • +
  • Updates RIDs used to generate component Inventory (#20370)
  • +
  • Bump XunitXml.TestLogger from 3.1.11 to 3.1.17 (#20293)
  • +
  • Update experimental-feature json files (#20335)
  • +
  • Use fxdependent-win-desktop runtime for compliance runs (#20326)
  • +
  • Release build: Change the names of the PATs (#20307)
  • +
  • Add mapping for mariner arm64 stable (#20213)
  • +
  • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken in the right order (#20306)
  • +
  • Enable vPack provenance data (#20220)
  • +
  • Bump actions/checkout from 3 to 4 (#20205)
  • +
  • Start using new packages.microsoft.com cli (#20140, #20141)
  • +
  • Add mariner arm64 to PMC release (#20176)
  • +
  • Fix typo donet to dotnet in build scripts and pipelines (#20122) (Thanks @0o001!)
  • +
  • Install the pmc cli
  • +
  • Add skip publish parameter
  • +
  • Add verbose to clone
  • +
+ +
+ +### Documentation and Help Content + +- Include information about upgrading in readme (#20993) +- Expand "iff" to "if-and-only-if" in XML doc content (#20852) +- Update LTS links in README.md to point to the v7.4 packages (#20839) (Thanks @kilasuit!) +- Update `README.md` to improve readability (#20553) (Thanks @AnkitaSikdar005!) +- Fix link in `docs/community/governance.md` (#20515) (Thanks @suravshresth!) +- Update `ADOPTERS.md` (#20555) (Thanks @AnkitaSikdar005!) +- Fix a typo in `ADOPTERS.md` (#20504, #20520) (Thanks @shruti-sen2004!) +- Correct grammatical errors in `README.md` (#20509) (Thanks @alienishi!) +- Add 7.3 changelog URL to readme (#20473) (Thanks @Saibamen!) +- Clarify some comments and documentation (#20462) (Thanks @darkstar!) + +[7.5.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.4.1...v7.5.0-preview.1 diff --git a/CHANGELOG/7.6.md b/CHANGELOG/7.6.md new file mode 100644 index 00000000000..cd09b76272c --- /dev/null +++ b/CHANGELOG/7.6.md @@ -0,0 +1,852 @@ +# 7.6 Changelog + +## [7.6.1] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27215) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27179) + +### Build and Packaging Improvements + +
+ + + +

Update to .NET SDK 10.0.202

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

Update to .NET SDK 10.0.201

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

Expand to see details.

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

Expand to see details.

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

We thank the following contributors!

+

@xtqqczze, @mawosoft, @ArmaanMcleod

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

We thank the following contributors!

+

@alerickson, @senerh, @RichardSlater, @xtqqczze

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

We thank the following contributors!

+

@ArmaanMcleod, @pressRtowin

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

We thank the following contributors!

+

@xtqqczze, @fMichaleczek, @ArmaanMcleod

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

We thank the following contributors!

+

@xtqqczze, @KyZy7

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

We thank the following contributors!

+

@xtqqczze

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

Updated SDK to 9.0.101

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

We thank the following contributors!

+

@eltociear, @JayBazuzi

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

We thank the following contributors!

+

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

+ +
+ +
    +
  • Deploy Box update (#24632)
  • +
  • Remove Regex use (#24235) (Thanks @MartinGC94!)
  • +
  • Improve cim ETS member inference completion (#24235) (Thanks @MartinGC94!)
  • +
  • Emit ProgressRecord in CLIXML minishell output (#21373) (Thanks @jborean93!)
  • +
  • Assign the value returned by the MaybeAdd method
  • (#24652) +
  • Add support for interface static abstract props (#21061) (Thanks @jborean93!)
  • +
  • Change call to optional add in the binder expression (#24451) (Thanks @jborean93!)
  • +
  • Turn off AMSI member invocation on nix release builds (#24451) (Thanks @jborean93!)
  • +
  • Bump github/codeql-action from 3.27.0 to 3.27.6 (#24639)
  • +
  • Update src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs (#24239) (Thanks @jborean93!)
  • +
  • Apply suggestions from code review (#24239) (Thanks @jborean93!)
  • +
  • Add remote runspace check for PushRunspace (#24239) (Thanks @jborean93!)
  • +
  • Set LangVersion compiler option to 13.0 (#24619) (Thanks @xtqqczze!)
  • +
  • Set LangVersion compiler option to 13.0 (#24617) (Thanks @xtqqczze!)
  • +
  • Update metadata.json for PowerShell 7.5 RC1 release (#24589)
  • +
  • Update nuget publish to use Deploy Box (#24596)
  • +
  • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583)
  • +
  • Update machine pool for copy blob and upload buildinfo stage (#24587)
  • +
  • Bump .NET 9 and dependencies (#24573)
  • +
  • Bump actions/dependency-review-action from 4.3.4 to 4.4.0 (#24503)
  • +
  • Bump actions/checkout from 4.2.1 to 4.2.2 (#24488)
  • +
  • Bump agrc/reminder-action from 1.0.14 to 1.0.15 (#24384)
  • +
  • Bump actions/upload-artifact from 4.4.0 to 4.4.3 (#24410)
  • +
  • Update branch for release (#24534)
  • +
  • Revert "Update package references (#24414)" (#24532)
  • +
  • Add a way to use only NuGet feed sources (#24528)
  • +
  • Update PSResourceGet to v1.1.0-RC2 (#24512) (Thanks @alerickson!)
  • +
  • Bump .NET to 9.0.100-rc.2.24474.11 (#24509)
  • +
  • Fix seed max value for Container Linux CI (#24510)
  • +
  • Update metadata.json for 7.2.24 and 7.4.6 releases (#24484)
  • +
  • Download package from package build for generating vpack (#24481)
  • +
  • Keep the roff file when gzipping it. (#24450)
  • +
  • Delete the msix blob if it's already there (#24353)
  • +
  • Add PMC mapping for debian 12 (bookworm) (#24413)
  • +
  • Checkin generated manpage (#24423)
  • +
  • Add CodeQL scanning to APIScan build (#24303)
  • +
  • Update package references (#24414)
  • +
  • Update vpack pipeline (#24281)
  • +
  • Bring changes from v7.5.0-preview.5 Release Branch to Master (#24369)
  • +
  • Bump agrc/create-reminder-action from 1.1.15 to 1.1.16 (#24375)
  • +
  • Add BaseUrl to buildinfo json file (#24376)
  • +
  • Update metadata.json (#24352)
  • +
  • Copy to static site instead of making blob public (#24269)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (Thanks @alerickson!)
  • +
  • add updated libicu dependency for debian packages (#24301)
  • +
  • add mapping to azurelinux repo (#24290)
  • +
  • Remove the MD5 branch in the strong name signing token calculation (#24288)
  • +
  • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273)
  • +
  • Ensure the official build files CodeQL issues (#24278)
  • +
  • Update experimental-feature json files (#24271)
  • +
  • Make some release tests run in a hosted pools (#24270)
  • +
  • Do not build the exe for Global tool shim project (#24263)
  • +
  • Update and add new NuGet package sources for different environments. (#24264)
  • +
  • Bump skitionek/notify-microsoft-teams (#24261)
  • +
  • Create new pipeline for compliance (#24252)
  • +
  • Capture environment better (#24148)
  • +
  • Add specific path for issues in tsaconfig (#24244)
  • +
  • Use Managed Identity for APIScan authentication (#24243)
  • +
  • Add windows signing for pwsh.exe (#24219)
  • +
  • Bump super-linter/super-linter from 7.0.0 to 7.1.0 (#24223)
  • +
  • Update the URLs used in nuget.config files (#24203)
  • +
  • Check Create and Submit in vPack build by default (#24181)
  • +
  • Replace PSVersion source generator with incremental one (#23815) (Thanks @iSazonov!)
  • +
  • Save man files in /usr/share/man instead of /usr/local/share/man (#23855) (Thanks @rzippo!)
  • +
  • Bump super-linter/super-linter from 6.8.0 to 7.0.0 (#24169)
  • +
+ +
+ +### Documentation and Help Content + +- Updated Third Party Notices (#24666) +- Update `HelpInfoUri` for 7.5 (#24610) +- Update changelog for v7.4.6 release (#24496) +- Update to the latest NOTICES file (#24259) +- Update the changelog `preview.md` (#24213) +- Update changelog readme with 7.4 (#24182) (Thanks @ThomasNieto!) +- Fix Markdown linting error (#24204) +- Updated changelog for v7.2.23 (#24196) (Internal 32131) +- Update changelog and `metadata.json` for v7.4.5 release (#24183) +- Bring 7.2 changelogs back to master (#24158) + +[7.6.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.6.0-preview.1 diff --git a/CHANGELOG/README.md b/CHANGELOG/README.md index 4c271de1a2c..c20cd311ff5 100644 --- a/CHANGELOG/README.md +++ b/CHANGELOG/README.md @@ -1,7 +1,11 @@ # Changelogs -* [Current preview changelog](preview.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 7d940524942..8f23d37c9bb 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,242 +1,61 @@ -# Current preview release +# Preview Changelog -## [7.1.0-preview.5] - 2020-07-06 - -### Engine Updates and Fixes - -- Ensure assemblies listed in the module manifest `FileList` field are not loaded (#12968) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@xtqqczze

- -
- -
    -
  • Code performance fixes (#12956) (Thanks @xtqqczze!)
  • -
- -
- -### Tools - -- Add missing `.editorconfig` settings present in `dotnet/runtime` (#12871) (Thanks @xtqqczze!) - -### Tests - -- Add new test for `Format-Custom` to avoid data loss (#11393) (Thanks @iSazonov!) - -### Build and Packaging Improvements - -
- - -

Fixed upgrade code in MSI package.

- -
-
    -
  • Change log for v7.1.0-preview.5 (Internal 11880)
  • -
  • Fix Path for the Preview MSI (#13070)
  • -
  • Correct stable and preview upgrade codes for MSI (#13036)
  • -
  • Changelog for `v7.1.0-preview.4` (Internal 11841)
  • -
  • Fix NuGet package compliance issues (#13045)
  • -
  • Bump xunit.runner.visualstudio from 2.4.1 to 2.4.2 (#12874)
  • -
  • Bump NJsonSchema from `10.1.21` to `10.1.23` (#13032) (#13022)
  • -
- -
- -### Documentation and Help Content - -- Fix links for MSI packages to point to `7.1.0-preview.3` (#13056) -- Add update `packages.microsoft.com` step to distribution request template. (#13008) -- Update `windows-core.md` (#13053) (Thanks @xtqqczze!) -- Add `@rjmholt` to maintainers list (#13033) -- Update docs for `v7.1.0-preview.4` release (#13028) - -## [7.1.0-preview.4] - 2020-06-25 +## [7.7.0-preview.1] ### Breaking Changes -- Make the switch parameter `-Qualifier` not positional for `Split-Path` (#12960) (Thanks @yecril71pl!) -- Resolve the working directory as literal path for `Start-Process` when it's not specified (#11946) (Thanks @NoMoreFood!) -- Make `-OutFile` parameter in web cmdlets to work like `-LiteralPath` (#11701) (Thanks @iSazonov!) +- Add `ValidateNotNullOrEmpty` attribute to the `-Property` of `Format-Table/List/Custom` (#26552) +- Fix to use accurate message for validating a string argument is not null and not an empty string (#26668) +- Correct handling of explicit `-[Operator]:$false` parameter values in `Where-Object` (#26485) (Thanks @yotsuda!) ### Engine Updates and Fixes -- Ensure null-coalescing LHS is evaluated only once (#12667) -- Fix path handling bug in `PSTask` (#12554) (Thanks @IISResetMe!) -- Remove extra line before formatting group (#12163) (Thanks @iSazonov!) -- Make module formatting not generate error with strict mode (#11943) -- Adding more ETW logs to WSMan plugin (#12798) (Thanks @krishnayalavarthi!) -- Restrict loading of `amsi.dll` to `system32` folder (#12730) - -### General Cmdlet Updates and Fixes - -- Fix `NullReferenceException` in `CommandSearcher.GetNextCmdlet` (#12659) (Thanks @powercode!) -- Prevent `NullReferenceException` in Unix computer cmdlets with test hooks active (#12651) (Thanks @vexx32!) -- Fix issue in `Select-Object` where `Hashtable` members (e.g. `Keys`) cannot be used with `-Property` or `-ExpandProperty` (#11097) (Thanks @vexx32!) -- Fix conflicting shorthand switch `-w` for pwsh (#12945) -- Rename the `CimCmdlet` resource file (#12955) (Thanks @iSazonov!) -- Remove use of `Test-Path` in `ConciseView` (#12778) -- Flag `default` switch statement condition clause as keyword (#10487) (Thanks @msftrncs!) -- Add parameter `SchemaFile` to `Test-Json` cmdlet (#11934) (Thanks @beatcracker!) -- Bring back Certificate provider parameters (#10622) (Thanks @iSazonov!) -- Fix `New-Item` to create symbolic link to relative path target (#12797) (Thanks @iSazonov!) -- Add `CommandLine` property to Process (#12288) (Thanks @iSazonov!) -- Adds `-MaskInput` parameter to `Read-Host` (#10908) (Thanks @davinci26!) -- Change `CimCmdlets` to use `AliasAttribute` (#12617) (Thanks @thlac!) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@xtqqczze, @sethvs, @romero126, @kvprasoon, @powercode

- -
- -
    -
  • Use nameof operator (#12716) (Thanks @xtqqczze!)
  • -
  • Fix comments in Mshexpression.cs (#12711) (Thanks @sethvs!)
  • -
  • Formatting: remove duplicate semicolons (#12666) (Thanks @xtqqczze!)
  • -
  • Replace SortedList with Generic.SortedList<TKey,TValue> (#12954) (Thanks @xtqqczze!)
  • -
  • Use HashSet instead of Hashtable with null values (#12958) (Thanks @xtqqczze!)
  • -
  • Rename CopyItem.Tests.ps1 to Copy-Item.Tests.ps1 to match other tests (#10701) (Thanks @romero126!)
  • -
  • Fix RCS1114: Remove redundant delegate creation (#12917) (Thanks @xtqqczze!)
  • -
  • Code redundancy fixes (#12916) (Thanks @xtqqczze!)
  • -
  • Update the PowerShell modules to use the new Help URI (#12686)
  • -
  • Reorder modifiers according to preferred order (#12864) (Thanks @xtqqczze!)
  • -
  • Expand numberOfPowershellRefAssemblies list capacity (#12840) (Thanks @xtqqczze!)
  • -
  • Add readonly modifier to internal static members (#11777) (Thanks @xtqqczze!)
  • -
  • cleanup: Use coalesce expression (#12829) (Thanks @xtqqczze!)
  • -
  • Add missing assessibility modifiers (#12820) (Thanks @xtqqczze!)
  • -
  • Use t_ naming convention for ThreadStatic members (#12826) (Thanks @xtqqczze!)
  • -
  • Formatting: Add empty line between declarations (#12824) (Thanks @xtqqczze!)
  • -
  • Clarify defaultRefAssemblies list capacity in AddType.cs (#12520) (Thanks @xtqqczze!)
  • -
  • Fixing "Double "period" (..) in message for System.InvalidOperationException" (#12758) (Thanks @kvprasoon!)
  • -
  • Rethrow to preserve stack details for better maintainability (#12723) (Thanks @xtqqczze!)
  • -
  • Delete license.rtf (#12738) (Thanks @xtqqczze!)
  • -
  • Nullable annotations for CommandSearcher (#12733) (Thanks @powercode!)
  • -
  • Redundancy: Remove 'partial' modifier from type with a single part (#12725) (Thanks @xtqqczze!)
  • -
  • Remove phrase 'All rights reserved' from Microsoft copyright statements (#12722) (Thanks @xtqqczze!)
  • -
  • IDictionary -> IDictionary<string, FunctionInfo> for FunctionTable (#12658) (Thanks @powercode!)
  • -
- -
- -### Tools - -- Use correct isError parameter with Write-Log (#12989) -- Disable `NonPrivateReadonlyFieldsMustBeginWithUpperCaseLetter` rule in `StyleCop` (#12855) (Thanks @xtqqczze!) -- Add @TylerLeonhardt to PowerShell team list to correct changelog generation (#12927) -- Enable the upload of `ETW` traces to `CLR CAP` in Windows daily build (#12890) -- Prevent GitHub workflow for daily dotnet build updates from running in forks (#12763) (Thanks @bergmeister!) -- Add GitHub action for PR creation and `Wix` file generation logic (#12748) - -### Tests - -- Remove duplicate tests from `Measure-Object.Tests.ps1` (#12683) (Thanks @sethvs!) -- Fix tests to not write errors to console (#13010) -- Make sure tabcompletion tests run (#12981) -- Remove dependency on DNS for `Test-Connection` tests on macOS (#12943) -- Restore `markdownlint` tests (#12549) (Thanks @xtqqczze!) -- Wrap tests in pester blocks (#12700) (Thanks @xtqqczze!) - -### Build and Packaging Improvements - -
- - - -

We thank the following contributors!

-

@iSazonov, @kvprasoon, @Saancreed, @heaths, @xtqqczze

- -
- -
    -
  • Update Distribution_Request.md
  • -
  • Bump NJsonSchema from 10.1.15 to 10.1.16 (#12685)
  • -
  • Disable uploading Symbols package (#12687)
  • -
  • Update .NET SDK version from 5.0.100-preview.5.20279.10 to 5.0.100-preview.6.20318.15 (#13018)
  • -
  • Remove component ref when re-generating the wix file (#13019)
  • -
  • Make sure icons are added to MSI staging folder (#12983)
  • -
  • Update DotnetRutimeMetadata.json to point to preview 6 (#12972)
  • -
  • Bump PSReadLine from 2.0.1 to 2.0.2 (#12909)
  • -
  • Bump NJsonSchema from 10.1.18 to 10.1.21 (#12944)
  • -
  • Check if Azure Blob exists before overwriting (#12921)
  • -
  • Enable skipped tests (#12894) (Thanks @iSazonov!)
  • -
  • Fix break in package build by pinning ffi version to 1.12 (#12889)
  • -
  • Upgrade APIScan version (#12876)
  • -
  • Make contributors unique in Release notes (#12878) (Thanks @kvprasoon!)
  • -
  • Update Linux daily CI to run in a single agent & collect traces (#12866)
  • -
  • Update .NET SDK version from 5.0.100-preview.5.20278.13 to 5.0.100-preview.5.20279.10 (#12844) (Thanks @github-actions[bot]!)
  • -
  • Sign the MSIX files for the store (#12582)
  • -
  • Update the CI builds (#12830)
  • -
  • Update .NET SDK version from 5.0.100-preview.5.20272.6 to 5.0.100-preview.5.20278.13 (#12772) (Thanks @github-actions[bot]!)
  • -
  • Allow use of build module on unknown Linux distros (#11146) (Thanks @Saancreed!)
  • -
  • Fix MSI upgrade and shortcut issues (#12792) (Thanks @heaths!)
  • -
  • Bump NJsonSchema from 10.1.17 to 10.1.18 (#12812)
  • -
  • Update .NET SDK version from 5.0.100-preview.5.20269.29 to 5.0.100-preview.5.20272.6 (#12759) (Thanks @github-actions[bot]!)
  • -
  • Bump NJsonSchema from 10.1.16 to 10.1.17 (#12761)
  • -
  • Update to dotnet SDK 5.0.0-preview.5.20268.9 (#12740)
  • -
  • Remove assets\license.rtf (#12721) (Thanks @xtqqczze!)
  • -
  • Bump Microsoft.CodeAnalysis.CSharp from 3.5.0 to 3.6.0 (#12731)
  • -
- -
- -### Documentation and Help Content - -- Update `README` and `metadata` files for next release (#12717) -- Update `README.md` removing experimental status of `Arm` builds, but `Win-Arm64` is still preview for Stable release. (#12707) -- Add link to Github compare in changelog (#12713) (Thanks @xtqqczze!) -- Added missing changelog for v7.1.0-preview.2 (#12665) -- Update required Visual Studio version in build docs (#12628) (Thanks @xtqqczze!) -- minor update to Distribution_Request.md (#12705) (Thanks @kilasuit!) -- Update docs.microsoft.com links (#12653) (Thanks @xtqqczze!) -- Update change log for `6.2.5` release (#12670) -- Update `README.md` and `metadata.json` for next release (#12668) -- Merge 7.0.1 change log (#12669) -- Remove markdown unused definitions (#12656) (Thanks @xtqqczze!) -- Add HoloLens to list of PowerShell adopters (#12940) (Thanks @reynoldsbd!) -- Update `README.md` and `metadata.json` for next releases (#12939) -- Fix broken link in `README.md` (#12887) (Thanks @xtqqczze!) -- Minor typo corrections in Distribution Request Issue Templates (#12744) (Thanks @corbob!) -- Correct 'review-for-comments' in `Governance.md` (#11035) (Thanks @MarvTheRobot!) -- Fix markdown ordered lists (#12657) (Thanks @xtqqczze!) -- Fix broken `docs.microsoft.com` link (#12776) (Thanks @xtqqczze!) -- Replace link to Slack with link to PowerShell Virtual User Group (#12786) (Thanks @xtqqczze!) -- Update `LICENSE.txt` so that it's recognized as MIT (#12729) - -## [7.1.0-preview.3] - 2020-05-14 - -### Breaking Changes - -- Fix string parameter binding for `BigInteger` numeric literals (#11634) (Thanks @vexx32!) - -### Engine Updates and Fixes - -- Set correct `PSProvider` full name at module load time (#11813) (Thanks @iSazonov!) - -### Experimental Features - -- Support passing `PSPath` to native commands (#12386) +- Update `MaxVisitCount` and `MaxHashtableKeyCount` if `VisitorSafeValueContext` indicates `SkipLimitCheck` is true +(#27308) +- Enable usage in AppContainers (#27305) +- Delay update notification for one week to ensure all packages become available (#27095) +- Fix up default value for parameters with the `in` modifier (#26785) (Thanks @jborean93!) +- Fix `WSManInstance` COM interface with `ResourceURI` (#26692) (Thanks @jborean93!) +- Refactor the module path construction code to make it more robust and easier to maintain (#26565) +- Fix checks for local user config file paths (#26269) ### General Cmdlet Updates and Fixes -- Fix incorrect index in format string in ParameterBinderBase (#12630) (Thanks @powercode!) -- Copy the `CommandInfo` property in `Command.Clone()` (#12301) (Thanks @TylerLeonhardt!) -- Apply `-IncludeEqual` in `Compa-Object` when `-ExcludeDifferent` is specified (#12317) (Thanks @davidseibel!) -- Change `Get-FileHash` to close file handles before writing output (#12474) (Thanks @HumanEquivalentUnit!) -- Fix inconsistent exception message in `-replace` operator (#12388) (Thanks @jackdcasey!) +- Add verbose message to `Get-Service` when properties cannot be returned (#27109) (Thanks @reabr!) +- Fix `Remove-Item` confirmation message to use provider path instead (#27123) (Thanks @scuzqy!) +- PSStyle: validate background index against `BackgroundColorMap` (#27106) (Thanks @cuiweixie!) +- Update PowerShell Profile DSC resource manifests to allow null for content (#26929) +- Add `SubjectAlternativeName` property to the `Signature` object returned from `Get-AuthenticodeSignature` (#26252) +- Mark `-NoTypeInformation` as obsolete no-op and evaluate `-IncludeTypeInformation` on by value on Csv cmdlets (#26719) (Thanks @yotsuda!) +- Support `TargetObject` position in `ParserErrors` (#26649) (Thanks @jborean93!) +- Fix the CLR internal error and null ref exception when running `show-command` with PowerShell API (#26669) +- Fix `Test-Json` false positive errors when using `oneOf` or `anyOf` in schema (#26618) (Thanks @yotsuda!) +- Add `ToRegex` method to `WildcardPattern` class (#26515) (Thanks @yotsuda!) +- Add `-ExcludeProperty` parameter to `Format-*` cmdlets (#26514) (Thanks @yotsuda!) +- Fix NOTES section formatting in comment-based help (#26512) (Thanks @yotsuda!) +- Disable AMSI content logging in release (#26235) (Thanks @xtqqczze!) +- Add tab completion for `$PSBoundParameters.Keys` switch cases and access patterns (#26483) (Thanks @yotsuda!) +- Fix formatting to properly handle the `Reset` VT sequences that appear in the middle of a string (#26424) +- Add `-Extension` parameter to `Join-Path` cmdlet (#26482) (Thanks @yotsuda!) +- Make `Export-Csv` `-Append` and `-NoHeader` mutually exclusive (#26472) (Thanks @yotsuda!) +- Respect `-Qualifier/-NoQualifier/-Leaf/-IsAbsolute:$false` in `Split-Path` (#26474) (Thanks @yotsuda!) +- Respect `-UseWindowsPowerShell:$false` in `New-PSSession` (#26469) (Thanks @yotsuda!) +- Respect `-Repeat/-MtuSize/-Traceroute:$false` in `Test-Connection` (#26479) (Thanks @yotsuda!) +- Fix `Invoke-RestMethod` to support read-only files in multipart form data (#26454) (Thanks @yotsuda!) +- Respect `-ListAvailable:$false` in `Get-TimeZone` (#26463) (Thanks @yotsuda!) +- Respect `-Shuffle:$false` in `Get-SecureRandom` (#26460) (Thanks @yotsuda!) +- Respect `-Shuffle:$false` in `Get-Random` (#26457) (Thanks @yotsuda!) +- DSC v3 resource for Powershell Profile (#26157) +- Make the experimental feature `PSFeedbackProvider` stable (#26343) +- Make some experimental features stable (#26348) +- Add `PSApplicationOutputEncoding` variable (#21219) (Thanks @jborean93!) +- Dynamically evaluate width of `LastWriteTime` for formatting output on Unix (#24624) (Thanks @MathiasMagnus!) +- Handle null reference exception in CsvCommands.cs: `ConvertPSObjectToCSV` (#26144) (Thanks @mikkas456!) +- Improve `ValidateLength` error message consistency and refactor validation tests (#25806) (Thanks @jorgeasaurus!) +- Correct handling of explicit `-Since:$false` parameter value in `Get-Uptime` (#26141) (Thanks @logiclrd!) +- Add property and event for debug attach (#25788) (Thanks @jborean93!) +- Fix memory leak in `GetFileShares` (#25896) (Thanks @xtqqczze!) +- Correct handling of explicit `-Empty:$false` parameter value in `New-Guid` (#26140) (Thanks @logiclrd!) ### Code Cleanup @@ -245,30 +64,81 @@

We thank the following contributors!

-

@xtqqczze, @RDIL, @powercode, @xtqqczze, @xtqqczze

+

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

    -
  • Replace Unicode non-breaking space character with space (#12576) (Thanks @xtqqczze!)
  • -
  • Remove unused New-DockerTestBuild.ps1 (#12610) (Thanks @RDIL!)
  • -
  • Annotate Assert methods for better code analysis (#12618) (Thanks @powercode!)
  • -
  • Use correct casing for cmdlet names and parameters in *.ps1 files throughout the codebase (#12584) (Thanks @xtqqczze!)
  • -
  • Document why PackageVersion is used in PowerShell.Common.props (#12523) (Thanks @xtqqczze!)
  • +
  • Fix IDisposable implementation in sealed classes (#26215) (Thanks @xtqqczze!)
  • +
  • Enable CA1852: Seal internal types (#25890) (Thanks @xtqqczze!)
  • +
  • Remove obsolete CA2006 rule suppression (#25939) (Thanks @xtqqczze!)
  • +
  • Use consistent indentation in the file HelpersCommon.psm1 (#26608)
  • +
  • Centralize ExcludeProperty filter application in ViewGenerator base class (#26574) (Thanks @yotsuda!)
  • +
  • Refactor IsComputerNameValid character validation (#26274) (Thanks @xtqqczze!)
  • +
  • Remove obsolete test/docker/networktest directory (#26388)
  • +
  • Avoid regex for exact word matching in DscClassCache (#26306) (Thanks @xtqqczze!)
  • +
  • Enable analyzers: Use char overload (#26301) (Thanks @xtqqczze!)
  • +
  • Enable CA1200: Avoid using cref tags with a prefix (#26298) (Thanks @xtqqczze!)
  • +
  • Remove unused timeout variable from RemoteHyperVTests class (#26297) (Thanks @xtqqczze!)
  • +
  • Enable CA2022: Avoid inexact read with Stream.Read (#25814) (Thanks @xtqqczze!)
  • +
  • Fix a few simple typos in comments and string outputs (#25805) (Thanks @ThioJoe!)
  • +
  • Remove unused Azure Devops windows CI workflows (#26245)
  • +
  • Fix CA1837: Use Environment.ProcessId (#26242) (Thanks @xtqqczze!)
  • +
  • Enable IDE0080: RemoveConfusingSuppressionForIsExpression (#26206) (Thanks @xtqqczze!)
  • +
  • Remove redundant CharSet from StructLayout attributes. Part 1 (#26216) (Thanks @xtqqczze!)
  • +
  • Fix IDE0083: UseNotPattern (#26213) (Thanks @xtqqczze!)
  • +
  • Fix IDE0049 for string in System.Management.Automation (#25921) (Thanks @xtqqczze!)
  • +
  • Fix IDE0049 for object in System.Management.Automation. Part 1 (#25923) (Thanks @xtqqczze!)
  • +
  • Replace stackallocs with collection expressions (#25803) (Thanks @xtqqczze!)
  • +
  • Capitalize Windows in PSNativeWindowsTildeExpansion experimental feature description (#25266) (Thanks @rwp0!)
  • +
  • Fix SA1028: Code should not contain trailing whitespace. Part 1. (#26203) (Thanks @xtqqczze!)
  • +
  • Fix IDE0083: UseNotPattern (#26209) (Thanks @xtqqczze!)
  • +
  • Fix CA1852: Seal internal types. Part 1 (#26205) (Thanks @xtqqczze!)
  • +
  • Enable IDE0019: InlineAsTypeCheck (#25920) (Thanks @xtqqczze!)
  • +
  • Fix mismatched indentation in .config/suppress.json (#26192) (Thanks @xtqqczze!)
  • +
  • Replace custom method with File.ReadAllText() in ScriptAnalysis.cs (#26060) (Thanks @amritanand-py!)
  • +
  • Avoid possible multiple enumerations in ImportModuleCommand.IsPs1xmlFileHelper_IsPresentInEntries (#26104) (Thanks @xtqqczze!)
  • +
  • Enable SA1206: Declaration keywords should follow order (#24973) (Thanks @xtqqczze!)
  • +
  • Disable IDE0049: PreferBuiltInOrFrameworkType (#26094) (Thanks @xtqqczze!)
  • +
  • Enable CA1853: Unnecessary call to Dictionary.ContainsKey(key) (#26106) (Thanks @xtqqczze!)
  • +
  • Enable CA1860: Avoid using Enumerable.Any() extension method (#26109) (Thanks @xtqqczze!)
  • +
  • Enable CA1858: Use StartsWith instead of IndexOf (#26107) (Thanks @xtqqczze!)
  • +
  • Add CodeQL suppressions for NativeCommandProcessor (#26729)
### Tools -- Update `@PoshChan` config to include `SSH` (#12526) (Thanks @vexx32!) -- Update log message in `Start-PSBootstrap` (#12573) (Thanks @xtqqczze!) -- Add the `.NET SDK` installation path to the current process path in `tools/UpdateDotnetRuntime.ps1` (#12525) +- Add GitOps policy to auto-label backport candidates when CL-BuildPackaging is added (#26881) +- Add Pester CI Analysis Skill (#26806) +- Delete unused winget release script (#26683) +- Improve error message from `Start-NativeExecution` (#26500) (Thanks @logiclrd!) +- Add default CODEOWNERS entry for maintainers (#26660) +- Add Attack Surface Analyzer Script (#26379) +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26350) +- Add reusable get-changed-files action and refactor existing actions (#26355) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26322) +- Create github copilot setup workflow (#26285) +- Update dependabot.yml to monitor release/* branches (#26251) ### Tests -- Make CIM tab completion test case insensitive (#12636) -- Mark ping tests as Pending due to stability issues in macOS (#12504) +- Fix the `PSNativeCommandArgumentPassing` test (#27057) +- Fix `Import-Module.Tests.ps1` to handle Arm32 platform (#26862) +- Add comprehensive PowerShell class tests for `ConvertTo-Json` (#26769) (Thanks @yotsuda!) +- Add comprehensive `PSCustomObject` tests for `ConvertTo-Json` (#26743) (Thanks @yotsuda!) +- Add GitHub Actions annotations for Pester test failures (#26789) +- Add comprehensive depth and multilevel composition tests for `ConvertTo-Json` (#26744) (Thanks @yotsuda!) +- Add comprehensive array and dictionary tests for `ConvertTo-Json` (#26742) (Thanks @yotsuda!) +- Add comprehensive scalar type tests for `ConvertTo-Json` (#26736) (Thanks @yotsuda!) +- Fix the fuzzy test (#26402) +- Add Fuzz Tests (#26384) +- Fix merge conflict checker for empty file lists and filter *.cs files (#26365) +- Fix linux_packaging job being skipped when only packaging files change (#26315) +- Use `[initialsessionstate]` type accelerator (#25912) (Thanks @xtqqczze!) +- Add markdown link verification for PRs (#26219) +- Check for `GetWindowPlacement` success (#26122) (Thanks @xtqqczze!) ### Build and Packaging Improvements @@ -277,306 +147,106 @@

We thank the following contributors!

-

@jcotton42, @iSazonov, @iSazonov, @iSazonov

+

@powercode, @kasperk81, @xtqqczze

    -
  • Update build to use the new .NET SDK 5.0.100-preview.4.20258.7 (#12637)
  • -
  • Bump NJsonSchema from 10.1.14 to 10.1.15 (#12608)
  • -
  • Bump NJsonSchema from 10.1.13 to 10.1.14 (#12598)
  • -
  • Bump NJsonSchema from 10.1.12 to 10.1.13 (#12583)
  • -
  • Update the build to sign any unsigned files as 3rd party Dlls (#12581)
  • -
  • Update .NET SDK to 5.0.100-preview.4.20229.10 (#12538)
  • -
  • Add ability to Install-Dotnet to specify directory (#12469)
  • -
  • Allow / in relative paths for using module (#7424) (#12492) (Thanks @jcotton42!)
  • -
  • Update dotnet metadata for next channel for automated updates (#12502)
  • -
  • Bump .NET to 5.0.0-preview.4 (#12507)
  • -
  • Bump Microsoft.ApplicationInsights from 2.13.1 to 2.14.0 (#12479)
  • -
  • Bump PackageManagement from 1.4.6 to 1.4.7 in /src/Modules (#12506)
  • -
  • Bump Xunit.SkippableFact from 1.3.12 to 1.4.8 (#12480)
  • -
  • Fix quotes to allow variable expansion (#12512)
  • -
  • Use new TargetFramework as net5.0 in packaging scripts (#12503) (Thanks @iSazonov!)
  • -
  • Use new value for TargetFramework as net5.0 instead of netcoreapp5.0 (#12486) (Thanks @iSazonov!)
  • -
  • Disable PublishReadyToRun for framework dependent packages (#12450)
  • -
  • Add dependabot rules to ignore updates from .NET (#12466)
  • -
  • Update README.md and metadata.json for upcoming release (#12441)
  • -
  • Turn on ReadyToRun (#12361) (Thanks @iSazonov!)
  • -
  • Add summary to compressed sections of change log (#12429)
  • +
  • Update branch for release (#27291)
  • +
  • Remove package verification from the notice pipeline (#27289)
  • +
  • Remove MSI from publishing pipeline (#27213)
  • +
  • Externalize findMissingNotices target framework selection with ordered Windows fallback (#27269)
  • +
  • Fix the package pipeline by adding in PDP-Media directory (#27254)
  • +
  • Bump actions/checkout from 4 to 6.0.2 (#27206)
  • +
  • Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#150) (#27209)
  • +
  • Pin ready-to-merge.yml reusable workflow to commit SHA (#27204)
  • +
  • Change the display name of PowerShell-LTS MSIX package to "PowerShell LTS" (#27203)
  • +
  • [StepSecurity] ci: Harden GitHub Actions (#27201)
  • +
  • [StepSecurity] ci: Harden GitHub Actions (#27202)
  • +
  • Redo windows image fix to use latest image (#27198)
  • +
  • Separate Store Package Creation, Skip Polling for Store Publish, Clean up PDP-Media (#27024)
  • +
  • Revert "Fetch latest ICU release version dynamically" (#27127)
  • +
  • Update package references and move to .NET SDK 11.0-preview.2 (#27117)
  • +
  • Add comment-based help documentation to build.psm1 functions (#27122) (Thanks @powercode!)
  • +
  • Bump github/codeql-action from 3.30.3 to 4.35.1 (#27120)
  • +
  • Select New MSIX Package Name (#27096)
  • +
  • Separate Official and NonOfficial templates for ADO pipelines (#26897)
  • +
  • Update the PhoneProductId to be the official LTS id used by Store (#27077)
  • +
  • release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27074)
  • +
  • Update build to create two msix's and msixbundles for LTS and Stable (#27056)
  • +
  • Update metadata.json for the v7.6.0 release (#27054)
  • +
  • Move _GetDependencies MSBuild target from dynamic generation in build.psm1 into Microsoft.PowerShell.SDK.csproj (#27052)
  • +
  • Fix PMC repo URL for RHEL10 (#27059)
  • +
  • Create Linux LTS deb/rpm packages for LTS releases (#27049)
  • +
  • Create LTS pkg and non-LTS pkg for macOS for LTS releases (#27039)
  • +
  • Fix the container image for vPack, MSIX vPack and Package pipelines (#27015)
  • +
  • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0 (#27003)
  • +
  • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26893)
  • +
  • Bump actions/upload-artifact from 4 to 7 (#26914)
  • +
  • Bump actions/dependency-review-action from 4.7.3 to 4.9.0 (#26938)
  • + +
  • Hardcode Official templates (#26928)
  • +
  • Add PMC packages for debian13 and rhel10 (#26912)
  • +
  • Split TPN manifest and Component Governance manifest (#26891)
  • +
  • Add version in description and pass store task on failure (#26885)
  • +
  • Correct the package name for .deb and .rpm packages (#26877)
  • +
  • Fix a preview detection test for the packaging script (#26882)
  • +
  • Exclude .exe packages from publishing to GitHub (#26859)
  • +
  • Update metadata.json for v7.6.0-rc.1 (#26856)
  • +
  • Fetch latest ICU release version dynamically (#26827) (Thanks @kasperk81!)
  • +
  • Update LangVersion to preview (#26214) (Thanks @xtqqczze!)
  • +
  • Update to .NET 11 SDK and update dependencies (#26783)
  • +
  • Update outdated package references (#26771)
  • +
  • Create es-metadata (#26759)
  • +
  • Add policy to restrict the Approved-LowRisk label (#26728)
  • +
  • Move PowerShell build to depend on .NET SDK 10.0.102 (#26697)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26380)
  • +
  • Update outdated package references (#26656)
  • +
  • Bring release changes from the v7.6.0-preview.6 release branch (#26627)
  • +
  • Update build to use .NET SDK 10.0.100 (#26448)
  • +
  • Update the macos package name for preview releases to match the previous pattern (#26429)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26427)
  • +
  • Fix template path for rebuild branch check in package.yml (#26425)
  • +
  • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26406)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26415)
  • +
  • Optimize/split windows package signing (#26403)
  • +
  • Improve ADO package build and validation across platforms (#26398)
  • +
  • Update outdated test package references (#26368)
  • +
  • Delete this way of collecting feedback (#26364)
  • +
  • Update the Microsoft.PowerShell.Native package version (#26347)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26326)
  • +
  • Bump actions/setup-dotnet from 4 to 5 (#26327)
  • +
  • Update SDK to 10.0.100-rc.2.25502.107 (#26305)
  • +
  • Replace fpm with dpkg-deb for DEB package generation (#26281)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26268)
  • +
  • Separate Store Automation Service Endpoints, Resolve AppID (#26210)
  • +
  • Update concurrency groups to prevent merge runs and pull request runs from canceling each other (#26257)
  • +
  • Update release tags to version 7.5.4 and 7.4.13 (#26258)
  • +
  • Update outdated package references (#26148)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26243)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26225)
  • +
  • Update vPack name (#26090)
  • +
  • Update metadata.json for v7.6.0-preview.5 release (#26158)
  • +
  • Bump ossf/scorecard-action from 2.4.2 to 2.4.3 (#26128)
### Documentation and Help Content -- Add link to life cycle doc to distribution request template (#12638) -- Update TFM reference in build docs (#12514) (Thanks @xtqqczze!) -- Fix broken link for blogs in documents (#12471) - -## [7.1.0-preview.2] - 2020-04-23 - -### Breaking Changes - -- On Windows, `Start-Process` creates a process environment with - all the environment variables from current session, - using `-UseNewEnvironment` creates a new default process environment (#10830) (Thanks @iSazonov!) -- Do not wrap return result to `PSObject` when converting ScriptBlock to delegate (#10619) - -### Engine Updates and Fixes - -- Allow case insensitive paths for determining `PSModulePath` (#12192) -- Add PowerShell version 7.0 to compatible version list (#12184) -- Discover assemblies loaded by `Assembly.Load(byte[])` and `Assembly.LoadFile` (#12203) - -### General Cmdlet Updates and Fixes - -- Fix `WinCompat` module loading to treat PowerShell 7 modules with higher priority (#12269) -- Implement `ForEach-Object -Parallel` runspace reuse (#12122) -- Fix `Get-Service` to not modify collection while enumerating it (#11851) (Thanks @NextTurn!) -- Clean up the IPC named pipe on PowerShell exit (#12187) -- Fix `` detection regex in web cmdlets (#12099) (Thanks @vexx32!) -- Allow shorter signed hex literals with appropriate type suffixes (#11844) (Thanks @vexx32!) -- Update `UseNewEnvironment` parameter behavior of `Start-Process` cmdlet on Windows (#10830) (Thanks @iSazonov!) -- Add `-Shuffle` switch to `Get-Random` command (#11093) (Thanks @eugenesmlv!) -- Make `GetWindowsPowerShellModulePath` compatible with multiple PS installations (#12280) -- Fix `Start-Job` to work on systems that don't have Windows PowerShell registered as default shell (#12296) -- Specifying an alias and `-Syntax` to `Get-Command` returns the aliased commands syntax (#10784) (Thanks @ChrisLGardner!) -- Make CSV cmdlets work when using `-AsNeeded` and there is an incomplete row (#12281) (Thanks @iSazonov!) -- In local invocations, do not require `-PowerShellVersion 5.1` for `Get-FormatData` in order to see all format data. (#11270) (Thanks @mklement0!) -- Added Support For Big Endian `UTF-32` (#11947) (Thanks @NoMoreFood!) -- Fix possible race that leaks PowerShell object dispose in `ForEach-Object -Parallel` (#12227) -- Add `-FromUnixTime` to `Get-Date` to allow Unix time input (#12179) (Thanks @jackdcasey!) -- Change default progress foreground and background colors to provide improved contrast (#11455) (Thanks @rkeithhill!) -- Fix `foreach -parallel` when current drive is not available (#12197) -- Do not wrap return result to `PSObject` when converting `ScriptBlock` to `delegate` (#10619) -- Don't write DNS resolution errors on `Test-Connection -Quiet` (#12204) (Thanks @vexx32!) -- Use dedicated threads to read the redirected output and error streams from the child process for out-of-proc jobs (#11713) - -### Code Cleanup - -
- - - -

We thank the following contributors!

-

@ShaydeNofziger, @RDIL

- -
- -
    -
  • Fix erroneous comment in tokenizer.cs (#12206) (Thanks @ShaydeNofziger!)
  • -
  • Fix terms checker issues (#12189)
  • -
  • Update copyright notice to latest guidance (#12190)
  • -
  • CodeFactor cleanup (#12251) (Thanks @RDIL!)
  • -
- -
- -### Tools - -- Update .NET dependency update script to include test `csproj` files (#12372) -- Scripts to update to .NET prerelease version (#12284) - -### Tests - -- Pin major Pester version to 4 to prevent breaking changes caused by upcoming release of v5 (#12262) (Thanks @bergmeister!) - -### Build and Packaging Improvements - -
- - - -

We thank the following contributors!

-

@rkitover, @bergmeister

- -
- -
    -
  • Add the nuget.config from root to the temporary build folder (#12394)
  • -
  • Bump System.IO.Packaging (#12365)
  • -
  • Bump Markdig.Signed from 0.18.3 to 0.20.0 (#12379)
  • -
  • Bump to .NET 5 Preview 3 pre-release (#12353)
  • -
  • Bump PowerShellGet from 2.2.3 to 2.2.4 (#12342)
  • -
  • Linux: Initial support for Gentoo installations. (#11429) (Thanks @rkitover!)
  • -
  • Upgrade to .NET 5 Preview 2 (#12250) (Thanks @bergmeister!)
  • -
  • Fix the Sync PSGalleryModules to Artifacts build (#12277)
  • -
  • Bump PSReadLine from 2.0.0 to 2.0.1 (#12243)
  • -
  • Bump NJsonSchema from 10.1.11 to 10.1.12 (#12230)
  • -
  • Update change log generation script to support collapsible sections (#12214)
  • -
- -
- -### Documentation and Help Content - -- Add documentation for `WebResponseObject` and `BasicHtmlWebResponseObject` properties (#11876) (Thanks @kevinoid!) -- Add Windows 10 IoT Core reference in `Adopters.md` (#12266) (Thanks @parameshbabu!) -- Update `README.md` and `metadata.json` for `7.1.0-preview.1` (#12211) - -## [7.1.0-preview.1] - 2020-03-26 - -### Breaking Changes - -- Use invariant culture string conversion for `-replace` operator (#10954) (Thanks @iSazonov!) - -### Engine Updates and Fixes - -- Revert the PRs that made `DBNull.Value` and `NullString.Value` treated as `$null` (#11648) - -### Experimental Features - -- Use invariant culture string conversion for `-replace` operator (#10954) (Thanks @iSazonov!) - -### General Cmdlet Updates and Fixes - -- Fix an operator preference order issue in binder code (#12075) (Thanks @DamirAinullin!) -- Fix `NullReferenceException` when binding common parameters of type `ActionPreference` (#12124) -- Fix default formatting for deserialized `MatchInfo` (#11728) (Thanks @iSazonov!) -- Use asynchronous streams in `Invoke-RestMethod` (#11095) (Thanks @iSazonov!) -- Address UTF-8 Detection In `Get-Content -Tail` (#11899) (Thanks @NoMoreFood!) -- Handle the `IOException` in `Get-FileHash` (#11944) (Thanks @iSazonov!) -- Change `PowerShell Core` to `PowerShell` in a resource string (#11928) (Thanks @alexandair!) -- Bring back `MainWindowTitle` in `PSHostProcessInfo` (#11885) (Thanks @iSazonov!) -- Miscellaneous minor updates to Windows Compatibility (#11980) -- Fix `ConciseView` to split `PositionMessage` using `[Environment]::NewLine` (#12010) -- Remove network hop restriction for interactive sessions (#11920) -- Fix `NullReferenceException` in `SuspendStoppingPipeline()` and `RestoreStoppingPipeline()` (#11870) (Thanks @iSazonov!) -- Generate GUID for `FormatViewDefinition` `InstanceId` if not provided (#11896) -- Fix `ConciseView` where error message is wider than window width and doesn't have whitespace (#11880) -- Allow cross-platform `CAPI-compatible` remote key exchange (#11185) (Thanks @silijon!) -- Fix error message (#11862) (Thanks @NextTurn!) -- Fix `ConciseView` to handle case where there isn't a console to obtain the width (#11784) -- Update `CmsCommands` to use Store vs certificate provider (#11643) (Thanks @mikeTWC1984!) -- Enable `pwsh` to work on Windows systems where `mpr.dll` and STA is not available (#11748) -- Refactor and implement `Restart-Computer` for `Un*x` and macOS (#11319) -- Add an implementation of `Stop-Computer` for Linux and macOS (#11151) -- Fix `help` function to check if `less` is available before using (#11737) -- Update `PSPath` in `certificate_format_ps1.xml` (#11603) (Thanks @xtqqczze!) -- Change regular expression to match relation-types without quotes in Link header (#11711) (Thanks @Marusyk!) -- Fix error message during symbolic link deletion (#11331) -- Add custom `Selected.*` type to `PSCustomObject` in `Select-Object` only once (#11548) (Thanks @iSazonov!) -- Add `-AsUTC` to the `Get-Date` cmdlet (#11611) -- Fix grouping behavior with Boolean values in `Format-Hex` (#11587) (Thanks @vexx32!) -- Make `Test-Connection` always use the default synchronization context for sending ping requests (#11517) -- Correct startup error messages (#11473) (Thanks @iSazonov!) -- Ignore headers with null values in web cmdlets (#11424) (Thanks @iSazonov!) -- Re-add check for `Invoke-Command` job dispose. (#11388) -- Revert "Update formatter to not write newlines if content is empty (#11193)" (#11342) (Thanks @iSazonov!) -- Allow `CompleteInput` to return results from `ArgumentCompleter` when `AST` or Script has matching function definition (#10574) (Thanks @M1kep!) -- Update formatter to not write new lines if content is empty (#11193) - -### Code Cleanup - -
- -
    -
  • Use span-based overloads (#11884) (Thanks @iSazonov!)
  • -
  • Use new string.Split() overloads (#11867) (Thanks @iSazonov!)
  • -
  • Remove unreachable DSC code (#12076) (Thanks @DamirAinullin!)
  • -
  • Remove old dead code from FullCLR (#11886) (Thanks @iSazonov!)
  • -
  • Use Dictionary.TryAdd() where possible (#11767) (Thanks @iSazonov!)
  • -
  • Use Environment.NewLine instead of hard-coded linefeed in ParseError.ToString (#11746)
  • -
  • Fix FileSystem provider error message (#11741) (Thanks @iSazonov!)
  • -
  • Reformat code according to EditorConfig rules (#11681) (Thanks @xtqqczze!)
  • -
  • Replace use of throw GetExceptionForHR with ThrowExceptionForHR (#11640) (Thanks @xtqqczze!)
  • -
  • Refactor delegate types to lambda expressions (#11690) (Thanks @xtqqczze!)
  • -
  • Remove Unicode BOM from text files (#11546) (Thanks @xtqqczze!)
  • -
  • Fix Typo in Get-ComputerInfo cmdlet description (#11321) (Thanks @doctordns!)
  • -
  • Fix typo in description for Get-ExperimentalFeature PSWindowsPowerShellCompatibility (#11282) (Thanks @alvarodelvalle!)
  • -
  • Cleanups in command discovery (#10815) (Thanks @iSazonov!)
  • -
  • Review CurrentCulture (#11044) (Thanks @iSazonov!)
  • -
- -
- -### Tools - -- Change recommended VS Code extension name from `ms-vscode.csharp` to `ms-dotnettools.csharp` (#12083) (Thanks @devlead!) -- Specify `csharp_preferred_modifier_order` in `EditorConfig` (#11775) (Thanks @xtqqczze!) -- Update `.editorconfig` (#11675) (Thanks @xtqqczze!) -- Enable `EditorConfig` support in `OmniSharp` (#11627) (Thanks @xtqqczze!) -- Specify charset in `.editorconfig` as `utf-8` (no BOM) (#11654) (Thanks @xtqqczze!) -- Configure the issue label bot (#11527) -- Avoid variable names that conflict with automatic variables (#11392) (Thanks @xtqqczze!) - -### Tests - -- Add empty `preview.md` file to fix broken link (#12041) -- Add helper functions for SSH remoting tests (#11955) -- Add new tests for `Get-ChildItem` for `FileSystemProvider` (#11602) (Thanks @iSazonov!) -- Ensure that types referenced by `PowerShellStandard` are present (#10634) -- Check state and report reason if it's not "opened" (#11574) -- Fixes for running tests on Raspbian (#11661) -- Unify pester test syntax for the arguments of `-BeOfType` (#11558) (Thanks @xtqqczze!) -- Correct casing for automatic variables (#11568) (Thanks @iSazonov!) -- Avoid variable names that conflict with automatic variables part 2 (#11559) (Thanks @xtqqczze!) -- Update pester syntax to v4 (#11544) (Thanks @xtqqczze!) -- Allow error 504 (Gateway Timeout) in `markdown-link` tests (#11439) (Thanks @xtqqczze!) -- Re-balance CI tests (#11420) (Thanks @iSazonov!) -- Include URL in the markdown-links test error message (#11438) (Thanks @xtqqczze!) -- Use CIM cmdlets instead of WMI cmdlets in tests (#11423) (Thanks @xtqqczze!) - -### Build and Packaging Improvements - -
- -
    -
  • Put symbols in separate package (#12169)
  • -
  • Disable x86 PDB generation (#12167)
  • -
  • Bump NJsonSchema from 10.1.5 to 10.1.11 (#12050) (#12088) (#12166)
  • -
  • Create crossgen symbols for Windows x64 and x86 (#12157)
  • -
  • Move to .NET 5 preview.1 (#12140)
  • -
  • Bump Microsoft.CodeAnalysis.CSharp from 3.4.0 to 3.5.0 (#12136)
  • -
  • Move to standard internal pool for building (#12119)
  • -
  • Fix package syncing to private Module Feed (#11841)
  • -
  • Add Ubuntu SSH remoting tests CI (#12033)
  • -
  • Bump Markdig.Signed from 0.18.1 to 0.18.3 (#12078)
  • -
  • Fix MSIX packaging to determine if a Preview release by inspecting the semantic version string (#11991)
  • -
  • Ignore last exit code in the build step as dotnet may return error when SDK is not installed (#11972)
  • -
  • Fix daily package build (#11882)
  • -
  • Fix package sorting for syncing to private Module Feed (#11838)
  • -
  • Set StrictMode version 3.0 (#11563) (Thanks @xtqqczze!)
  • -
  • Bump .devcontainer version to dotnet 3.1.101 (#11707) (Thanks @Jawz84!)
  • -
  • Move to version 3 of AzFileCopy (#11697)
  • -
  • Update README.md and metadata.json for next release (#11664)
  • -
  • Code Cleanup for environment data gathering in build.psm1 (#11572) (Thanks @xtqqczze!)
  • -
  • Update Debian Install Script To Support Debian 10 (#11540) (Thanks @RandomNoun7!)
  • -
  • Update ADOPTERS.md (#11261) (Thanks @edyoung!)
  • -
  • Change back to use powershell.exe in 'SetVersionVariables.yml' to unblock daily build (#11207)
  • -
  • Change to use pwsh to have consistent JSON conversion for DateTime (#11126)
  • -
- -
- -### Documentation and Help Content - -- Replace `VSCode` link in `CONTRIBUTING.md` (#11475) (Thanks @stevend811!) -- Remove the version number of PowerShell from LICENSE (#12019) -- Add the 7.0 change log link to `CHANGELOG/README.md` (#12062) (Thanks @LabhanshAgrawal!) -- Improvements to the contribution guide (#12086) (Thanks @ShaydeNofziger!) -- Update the doc about debugging dotnet core in VSCode (#11969) -- Update `README.md` and `metadata.json` for the next release (#11918) (#11992) -- Update `Adopters.md` to include info on Azure Pipelines and GitHub Actions (#11888) (Thanks @alepauly!) -- Add information about how Amazon AWS uses PowerShell. (#11365) (Thanks @bpayette!) -- Add link to .NET CLI version in build documentation (#11725) (Thanks @joeltankam!) -- Added info about `DeploymentScripts` in `ADOPTERS.md` (#11703) -- Update `CHANGELOG.md` for `6.2.4` release (#11699) -- Update `README.md` and `metadata.json` for next release (#11597) -- Update the breaking change definition (#11516) -- Adding System Frontier to the PowerShell Core adopters list `ADOPTERS.md` (#11480) (Thanks @OneScripter!) -- Update `ChangeLog`, `README.md` and `metadata.json` for `7.0.0-rc.1` release (#11363) -- Add `AzFunctions` to `ADOPTERS.md` (#11311) (Thanks @Francisco-Gamino!) -- Add `Universal Dashboard` to `ADOPTERS.md` (#11283) (Thanks @adamdriscoll!) -- Add `config.yml` for `ISSUE_TEMPLATE` so that Doc, Security, Support, and Windows PowerShell issues go to URLs (#11153) -- Add `Adopters.md` file (#11256) -- Update `Readme.md` for `preview.6` release (#11108) -- Update `SUPPORT.md` (#11101) (Thanks @mklement0!) -- Update `README.md` (#11100) (Thanks @mklement0!) - -[7.1.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.4...v7.1.0-preview.5 -[7.1.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.3...v7.1.0-preview.4 -[7.1.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.2...v7.1.0-preview.3 -[7.1.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.1.0-preview.1...v7.1.0-preview.2 -[7.1.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.0.0-preview.6...v7.1.0-preview.1 - +- Check in `7.6.md` after v7.6.0 release (#27063) +- Update changelog for release v7.5.5 (#27014) +- Add 7.4.14 changelog (#26998) +- Update `SECURITY.md` to remove email reporting option (#26653) +- Update changelog for the release v7.6.0-preview.6 (#26597) +- Explain the parameter `-UseNuGetOrg` in build documentation (#26507) (Thanks @logiclrd!) +- Update backport prompt (#26392) +- Add a backport prompt for copilot (#26383) +- Update `linux.md` documentation to reflect current CI build configuration (#26255) +- Add GitHub Copilot instruction files for PowerShell CI build system (#26253) +- Add documentation for publishing Pester test results in GitHub Actions (#26254) +- Remove Gitter from README (#26200) (Thanks @xtqqczze!) +- Remove nightly build status section from README.md (#26227) (Thanks @xtqqczze!) +- Update changelog for v7.5.4 and v7.4.13 (#26202) + +[7.7.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-rc.1...v7.7.0-preview.1 diff --git a/CHANGELOG/v7.7/dependencychanges.json b/CHANGELOG/v7.7/dependencychanges.json new file mode 100644 index 00000000000..21cd1577251 --- /dev/null +++ b/CHANGELOG/v7.7/dependencychanges.json @@ -0,0 +1,15 @@ +[ + { + "ChangeType": "NonSecurity", + "Branch": "master", + "PackageId": ".NET SDK", + "FromVersion": "11.0.100-preview.2.26159.112", + "ToVersion": "11.0.100-preview.3.26207.106", + "VulnerabilityId": [], + "Severity": [], + "VulnerableRanges": [], + "AdvisoryUrls": [], + "Justification": "Updated .NET SDK. Building with the latest SDK is required.", + "TimestampUtc": "2026-04-17T17:16:15.7099916Z" + } +] diff --git a/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 ffd8f438071..f8288b53b67 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -1,8 +1,15 @@ { "sdk": { - "channel": "release/5.0.1xx-preview7", - "packageVersionPattern": "5.0.0-preview.7", - "sdkImageVersion": "5.0.100-preview.7", - "nextChannel": "net5/preview7" + "channel": "9.0.1xx-preview6", + "quality": "daily", + "qualityFallback": "preview", + "packageVersionPattern": "9.0.0-preview.6", + "sdkImageVersion": "11.0.100-preview.3.26207.106", + "nextChannel": "9.0.0-preview.7", + "azureFeed": "", + "sdkImageOverride": "" + }, + "internalfeed": { + "url": "" } } diff --git a/PowerShell.Common.props b/PowerShell.Common.props index 520dc622298..d71432aed2f 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -1,4 +1,5 @@ + + + + ^((\d+).(\d+).(\d+))(-(\w+)(.(\d+))?)?$ + $([System.Text.RegularExpressions.Regex]::Match($(ReleaseTag), $(RegexReleaseTag)).Groups[1].Value) + $([System.Text.RegularExpressions.Regex]::Match($(ReleaseTag), $(RegexReleaseTag)).Groups[8].Value) + $([System.Text.RegularExpressions.Regex]::Match($(ReleaseTag), $(RegexReleaseTag)).Groups[6].Value) + + 100 + + 500 + $([MSBuild]::Add($(ReleaseTagSemVersionPart), $(RCIncrementValue))) + $(ReleaseTag) + + $(ReleaseTagVersionPart).$(ReleaseTagSemVersionPart) + + $(ReleaseTagVersionPart).$(GAIncrementValue) + + $(PSCoreFileVersion) + $([System.Version]::Parse($(PSCoreFileVersion)).Major).$([System.Version]::Parse($(PSCoreFileVersion)).Minor).0.$([System.Version]::Parse($(PSCoreFileVersion)).Revision) ^v(.+)-(\d+)-g(.+) + $([System.Text.RegularExpressions.Regex]::Match($(PowerShellVersion), $(RegexGitVersion)).Groups[1].Value) $([System.Text.RegularExpressions.Regex]::Match($(PowerShellVersion), $(RegexGitVersion)).Groups[1].Value) $([System.Text.RegularExpressions.Regex]::Match($(PowerShellVersion), $(RegexGitVersion)).Groups[2].Value) $([System.Text.RegularExpressions.Regex]::Match($(PowerShellVersion), $(RegexGitVersion)).Groups[3].Value) @@ -48,10 +89,10 @@ --> - $(PSCoreBuildVersion) + $(PSCoreFileVersion) $(PSCoreFormattedVersion) $(PSCoreFormattedVersion) @@ -63,15 +104,22 @@ $(PSCoreBuildVersion) ..\..\assets\Powershell_av_colors.ico - ..\..\assets\Powershell_black.ico + ..\..\assets\Powershell_avatar.ico + ..\..\assets\Powershell_black.ico + + + @@ -126,30 +176,59 @@ portable + + + + EnvironmentVariable;Global + false + false + + + + + Global + + + + + true + true + + + + AppLocal + + + + + true + true + + true + portable - - + + true full - - - - false - portable + + strict - - - - portable + + true diff --git a/PowerShell.sln b/PowerShell.sln index b164361d7d2..4938316281d 100644 --- a/PowerShell.sln +++ b/PowerShell.sln @@ -1,5 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 +# Visual Studio Version 16 # https://github.com/dotnet/project-system/blob/master/docs/opening-with-new-project-system.md#project-type-guids VisualStudioVersion = 15.0.26730.12 MinimumVisualStudioVersion = 10.0.40219.1 @@ -29,10 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WSMan.Runtime", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "powershell-unix", "src\powershell-unix\powershell-unix.csproj", "{73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerShell.MarkdownRender", "src\Microsoft.PowerShell.MarkdownRender\Microsoft.PowerShell.MarkdownRender.csproj", "{43D4F8DA-A7DE-494B-81B0-BDE3CFD7B1F1}" -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 7b79f00f785..a7b31c475f8 100644 --- a/README.md +++ b/README.md @@ -1,211 +1,102 @@ # ![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][rl-windows-64] | [.msi][rl-windows-64] | [.msi][pv-windows-64] | [Instructions][in-windows] | -| [Windows (x86)][corefx-win] | [.msi][rl-windows-86] | [.msi][rl-windows-86] | [.msi][pv-windows-86] | [Instructions][in-windows] | -| [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] | | -| [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) | [32-bit][rl-winarm]/[64-bit][rl-winarm64] (preview) | [32-bit][pv-winarm]/[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-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts_7.0.3-1.ubuntu.18.04_amd64.deb -[lts-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts_7.0.3-1.ubuntu.16.04_amd64.deb -[lts-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts_7.0.3-1.debian.9_amd64.deb -[lts-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts_7.0.3-1.debian.10_amd64.deb -[lts-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts-7.0.3-1.rhel.7.x86_64.rpm -[lts-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts-7.0.3-1.centos.8.x86_64.rpm -[lts-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-lts-7.0.3-osx-x64.pkg - -[rl-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x64.msi -[rl-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x86.msi -[rl-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell_7.0.3-1.ubuntu.18.04_amd64.deb -[rl-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell_7.0.3-1.ubuntu.16.04_amd64.deb -[rl-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell_7.0.3-1.debian.9_amd64.deb -[rl-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell_7.0.3-1.debian.10_amd64.deb -[rl-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-1.rhel.7.x86_64.rpm -[rl-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-1.centos.8.x86_64.rpm -[rl-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-osx-x64.pkg -[rl-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-arm32.zip -[rl-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-arm64.zip -[rl-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x86.zip -[rl-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/PowerShell-7.0.3-win-x64.zip -[rl-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-osx-x64.tar.gz -[rl-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-linux-x64.tar.gz -[rl-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-linux-arm32.tar.gz -[rl-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.0.3/powershell-7.0.3-linux-arm64.tar.gz -[rl-snap]: https://snapcraft.io/powershell - -[pv-windows-64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-x64.msi -[pv-windows-86]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-x86.msi -[pv-ubuntu18]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview_7.1.0-preview.5-1.ubuntu.18.04_amd64.deb -[pv-ubuntu16]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview_7.1.0-preview.5-1.ubuntu.16.04_amd64.deb -[pv-debian9]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview_7.1.0-preview.5-1.debian.9_amd64.deb -[pv-debian10]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview_7.1.0-preview.5-1.debian.10_amd64.deb -[pv-centos]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview-7.1.0_preview.5-1.rhel.7.x86_64.rpm -[pv-centos8]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-preview-7.1.0_preview.5-1.centos.8.x86_64.rpm -[pv-macos]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-7.1.0-preview.5-osx-x64.pkg -[pv-winarm]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-arm32.zip -[pv-winarm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-arm64.zip -[pv-winx86-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-x86.zip -[pv-winx64-zip]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/PowerShell-7.1.0-preview.5-win-x64.zip -[pv-macos-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-7.1.0-preview.5-osx-x64.tar.gz -[pv-linux-tar]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-7.1.0-preview.5-linux-x64.tar.gz -[pv-arm32]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-7.1.0-preview.5-linux-arm32.tar.gz -[pv-arm64]: https://github.com/PowerShell/PowerShell/releases/download/v7.1.0-preview.5/powershell-7.1.0-preview.5-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-deb9]: https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux#debian-9 -[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/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 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. + +Create or join a [discussion](https://github.com/PowerShell/PowerShell/discussions). + ## Chat Want to chat with other members of the PowerShell community? -We have a Gitter Room which you can join below. +There are dozens of topic-specific channels on our community-driven PowerShell Virtual User Group, which you can join on: -[![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) +* [Discord](https://discord.gg/PowerShell) +* [IRC](https://web.libera.chat/#powershell) on Libera.Chat +* [Slack](https://aka.ms/psslack) -There is also the community driven PowerShell Virtual User Group, which you can join on: +## Developing and Contributing -* [Slack](https://aka.ms/psslack) -* [Discord](https://aka.ms/psdiscord) +Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. -## Add-ons and libraries +If you are developing .NET Core C# applications targeting PowerShell Core, [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. -[Awesome PowerShell](https://github.com/janikvonrotz/awesome-powershell) has a great curated list of add-ons and resources. +Also, make sure to check out our [PowerShell-RFC repository](https://github.com/powershell/powershell-rfc) for request-for-comments (RFC) documents to submit and give comments on proposed and future designs. + +[Contribution Guide]: .github/CONTRIBUTING.md +[check out our FAQ]: docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package -## Building the Repository +## Building PowerShell | 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 +If you have any problems building PowerShell, please start by consulting the developer [FAQ]. -| 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 - -[windows-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=32 -[linux-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=23 -[macos-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=24 -[windows-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-Windows-daily -[linux-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-linux-daily?branchName=master -[macOS-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-macos-daily?branchName=master -[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 +[bd-linux]: docs/building/linux.md +[bd-windows]: docs/building/windows-core.md +[bd-macOS]: docs/building/macos.md +[FAQ]: docs/FAQ.md ## Downloading the Source Code -You can just clone the repository: +You can clone the repository: ```sh git clone https://github.com/PowerShell/PowerShell.git ``` -See [working with the PowerShell repository](https://github.com/PowerShell/PowerShell/tree/master/docs/git) for more information. - -## Developing and Contributing - -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. - -Also, make sure to check out our [PowerShell-RFC repository](https://github.com/powershell/powershell-rfc) for request-for-comments (RFC) documents to submit and give comments on proposed and future designs. - -[Contribution Guide]: https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md -[check out our FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package +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 @@ -215,31 +106,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 -Governance policy for 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..4d033a0f682 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -15,251 +15,429 @@ 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.45.0 - BSD-2-Clause + + + +Copyright (c) . All rights reserved. -Microsoft.CodeAnalysis.Common 3.3.1 - Apache-2.0 -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -Apache License + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Version 2.0, January 2004 + 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. -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND 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. - 1. Definitions. +--------------------------------------------------------- - +--------------------------------------------------------- - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +Humanizer.Core 2.14.1 - MIT - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. +Copyright .NET Foundation and Contributors +Copyright (c) .NET Foundation and Contributors - +MIT 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. +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: - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. +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. - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +--------------------------------------------------------- - +--------------------------------------------------------- - "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. +Json.More.Net 2.1.1 - MIT - - "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). +Copyright (c) .NET Foundation and Contributors - +MIT License - "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 - +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - "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." +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. - "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. - 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. +--------------------------------------------------------- - 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. +--------------------------------------------------------- - 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: +JsonPointer.Net 5.3.1 - MIT - (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 +Copyright (c) .NET Foundation and Contributors - (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 +MIT License - (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. +Copyright (c) .NET Foundation and Contributors - 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. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: - 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. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. - 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. - 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. +--------------------------------------------------------- - 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 +--------------------------------------------------------- -APPENDIX: How to apply the Apache License to your work. +JsonSchema.Net 7.4.0 - MIT -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] +Copyright (c) .NET Foundation and Contributors -Licensed under the Apache License, Version 2.0 (the "License"); +MIT License -you may not use this file except in compliance with the License. +Copyright (c) .NET Foundation and Contributors -You may obtain a copy of the License at +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -http://www.apache.org/licenses/LICENSE-2.0 +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -Unless required by applicable law or agreed to in writing, 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. -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. +Microsoft.ApplicationInsights 2.23.0 - MIT -------------------------------------------------------------------- -------------------------------------------------------------------- +(c) Microsoft Corporation -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 +MIT License -Apache License +Copyright (c) -Version 2.0, January 2004 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - 1. Definitions. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - +--------------------------------------------------------- - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. +--------------------------------------------------------- - +Microsoft.Bcl.AsyncInterfaces 10.0.3 - MIT - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the 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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 - "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. +MIT License - +Copyright (c) - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by 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: - +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 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. +--------------------------------------------------------- - +Microsoft.CodeAnalysis.Common 5.0.0 - MIT - "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). - +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors - "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. +MIT License - +Copyright (c) - "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." +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/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. - "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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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. +--------------------------------------------------------- - 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. +--------------------------------------------------------- - 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: +Microsoft.CodeAnalysis.CSharp 5.0.0 - MIT - (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) 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 - (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 +MIT License - (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. +Copyright (c) - 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. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - 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. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - 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. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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. +--------------------------------------------------------- - 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. +--------------------------------------------------------- - 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 +Microsoft.Extensions.ObjectPool 10.0.3 - MIT -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 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) -Copyright [yyyy] [name of copyright owner] +MIT License -Licensed under the Apache License, Version 2.0 (the "License"); +Copyright (c) -you may not use this file except in compliance with 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: -You may obtain a copy of the License at +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -http://www.apache.org/licenses/LICENSE-2.0 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -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. +Microsoft.PowerShell.MarkdownRender 7.2.1 - MIT -See the License for the specific language governing permissions and -limitations under the License. +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Markdown Rendering project PowerShell Markdown Renderer -------------------------------------------------------------------- +MIT License -------------------------------------------------------------------- +Copyright (c) -Markdig.Signed 0.17.1 - BSD-2-Clause -(c) 2008 VeriSign, Inc. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/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) . All rights reserved. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - 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 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. +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.ApplicationInsights 2.11.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. +--------------------------------------------------------- + +Microsoft.Win32.Registry.AccessControl 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 MIT License @@ -271,66 +449,88 @@ 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. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.NETCore.Platforms 3.0.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +Microsoft.Win32.SystemEvents 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation Copyright (c) 2011, Google Inc. -(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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +Microsoft.Windows.Compatibility 10.0.3 - MIT -------------------------------------------------------------------- -Microsoft.PowerShell.Native 7.0.0-preview.2 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) by P.J. Plauger +(c) Microsoft Corporation MIT License @@ -342,220 +542,325 @@ 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. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Microsoft.Win32.Registry 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. +Newtonsoft.Json 13.0.4 - MIT + + +Copyright James Newton-King 2008 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 +Copyright (c) James Newton-King 2008 +Copyright James Newton-King 2008 Json.NET The MIT License (MIT) -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +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: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/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. +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION 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 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. +runtime.android-arm.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation Copyright (c) 2011, Google Inc. -(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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.android-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -Microsoft.Win32.SystemEvents 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.android-x64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -Microsoft.Windows.Compatibility 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.android-x86.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -Namotion.Reflection 1.0.7 - 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. +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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 MIT License @@ -567,47 +872,69 @@ 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. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -Newtonsoft.Json 12.0.2 - MIT -(c) 2008 VeriSign, Inc. -Copyright James Newton-King 2008 -Copyright (c) 2007 James Newton-King -Copyright (c) James Newton-King 2008 +runtime.linux-arm.runtime.native.System.IO.Ports 10.0.3 - MIT -The MIT License (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 - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/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 +Copyright (c) 1991-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 MIT License @@ -619,442 +946,691 @@ 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. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -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. +runtime.linux-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation Copyright (c) 2011, Google Inc. -(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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-musl-arm.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -runtime.native.System.Data.SqlClient.sni 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -runtime.native.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-musl-x64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -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. +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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.linux-x64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -System.CodeDom 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT -------------------------------------------------------------------- -System.Collections.Immutable 1.5.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. -(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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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) 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 + +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.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. +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation Copyright (c) .NET Foundation Contributors Copyright (c) .NET Foundation and Contributors Copyright (c) 2011 Novell, Inc (http://www.novell.com) Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers The MIT License (MIT) @@ -1081,734 +1657,1071 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.osx-arm64.runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +runtime.osx-x64.runtime.native.System.IO.Ports 10.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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.CodeDom 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.ComponentModel.Composition 10.0.3 - MIT -------------------------------------------------------------------- -System.Data.OleDb 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.ComponentModel.Composition.Registration 10.0.3 - MIT -------------------------------------------------------------------- -System.Data.SqlClient 4.7.0 - MIT -2008 SQL Server 2012 -(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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Configuration.ConfigurationManager 10.0.3 - MIT -------------------------------------------------------------------- -System.Diagnostics.EventLog 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Data.Odbc 10.0.3 - MIT -------------------------------------------------------------------- -System.Diagnostics.PerformanceCounter 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Data.OleDb 10.0.3 - MIT -------------------------------------------------------------------- -System.DirectoryServices 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Data.SqlClient 4.9.0 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.EventLog 10.0.3 - MIT -------------------------------------------------------------------- -System.DirectoryServices.AccountManagement 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Diagnostics.PerformanceCounter 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.DirectoryServices 10.0.3 - MIT -------------------------------------------------------------------- -System.Drawing.Common 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.DirectoryServices.AccountManagement 10.0.3 - MIT -------------------------------------------------------------------- -System.IO.FileSystem.AccessControl 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.DirectoryServices.Protocols 10.0.3 - MIT -------------------------------------------------------------------- -System.IO.Packaging 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Drawing.Common 10.0.3 - MIT -------------------------------------------------------------------- -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 +(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) @@ -1834,487 +2747,756 @@ LIABILITY, WHETHER IN AN ACTION 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 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.IO.Ports 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Management 10.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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Net.Http.WinHttpHandler 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +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 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/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. -------------------------------------------------------------------- +--------------------------------------------------------- -System.Private.ServiceModel 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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) +System.Reflection.Context 10.0.3 - MIT -Copyright (c) .NET Foundation and Contributors -All rights reserved. +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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/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. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.Caching 10.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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Security.Cryptography.Pkcs 10.0.3 - MIT -------------------------------------------------------------------- -System.Reflection.DispatchProxy 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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Security.Cryptography.ProtectedData 10.0.3 - MIT -------------------------------------------------------------------- -System.Reflection.Emit 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Security.Cryptography.Xml 10.0.3 - MIT -------------------------------------------------------------------- -System.Reflection.Emit.ILGeneration 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Security.Permissions 10.0.3 - MIT -------------------------------------------------------------------- -System.Reflection.Emit.Lightweight 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.ServiceModel.Http 10.0.652802 - MIT -------------------------------------------------------------------- -System.Reflection.Metadata 1.6.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) Provided The MIT License (MIT) @@ -2341,32 +3523,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- -System.Runtime.Caching 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.NetFramingBase 10.0.652802 - 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) Provided The MIT License (MIT) @@ -2393,15 +3559,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.ServiceModel.NetTcp 10.0.652802 - MIT -System.ServiceModel.Duplex 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) @@ -2428,15 +3595,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.ServiceModel.Primitives 10.0.652802 - MIT -System.ServiceModel.Http 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) @@ -2463,85 +3631,238 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Syndication 10.0.3 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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 + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceProcess.ServiceController 10.0.3 - MIT -------------------------------------------------------------------- -System.ServiceModel.NetTcp 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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) 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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Speech 10.0.3 - MIT -------------------------------------------------------------------- -System.ServiceModel.Primitives 4.6.0 - MIT -(c) 2008 VeriSign, Inc. -(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) 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-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +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) 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +--------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +System.Web.Services.Description 8.1.2 - MIT -------------------------------------------------------------------- -System.ServiceModel.Security 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) @@ -2568,38 +3889,96 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------- +--------------------------------------------------------- -------------------------------------------------------------------- +--------------------------------------------------------- + +System.Windows.Extensions 10.0.3 - MIT -System.Threading.AccessControl 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-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula Copyright (c) 2005-2007, Nick Galbreath -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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers 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) +MIT License -Copyright (c) .NET Foundation and Contributors +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. + +--------------------------------------------------------- -All rights reserved. + +------------------------------------------------------------------- + +------------------------------------------------------------------- + +Additional - + +------------------------------------------------- +Microsoft.PowerShell.Archive +------------------------------------------------- + +Copyright (c) 2016 Microsoft Corporation. + +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 +3987,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 +4064,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 +4072,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 +4100,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 +4132,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 +4150,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 +4158,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..dfcd95935d9 100644 --- a/assets/AppxManifest.xml +++ b/assets/AppxManifest.xml @@ -20,7 +20,7 @@ - + @@ -43,7 +43,11 @@ + + + + diff --git a/assets/Product.wxs b/assets/Product.wxs deleted file mode 100644 index 36e39a34e75..00000000000 --- a/assets/Product.wxs +++ /dev/null @@ -1,392 +0,0 @@ - - - = 601" ?> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Installed AND NOT UPGRADINGPRODUCTCODE - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - LAUNCHAPPONEXIT=1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ADD_PATH=1 - - - - - - - - - ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The application is distributed under the MIT license.]]> - - - Please review the ThirdPartyNotices.txt]]> - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - "1"]]> - - NOT Installed - Installed AND PATCH - - 1 - 1 - - 1 - 1 - NOT WIXUI_DONTVALIDATEPATH - "1"]]> - WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" - 1 - 1 - 1 - - NOT Installed - Installed AND NOT PATCH - Installed AND PATCH - - 1 - - 1 - 1 - 1 - - - - - - - diff --git a/assets/WixUIBannerBmp.png b/assets/WixUIBannerBmp.png deleted file mode 100644 index 2a1922c1d7c..00000000000 Binary files a/assets/WixUIBannerBmp.png and /dev/null differ diff --git a/assets/WixUIDialogBmp.png b/assets/WixUIDialogBmp.png deleted file mode 100644 index 57cf3734c17..00000000000 Binary files a/assets/WixUIDialogBmp.png and /dev/null differ diff --git a/assets/WixUIInfoIco.png b/assets/WixUIInfoIco.png deleted file mode 100644 index cb705d5f33f..00000000000 Binary files a/assets/WixUIInfoIco.png and /dev/null differ 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/default.help.txt b/assets/default.help.txt new file mode 100644 index 00000000000..a5b5cefc9cd --- /dev/null +++ b/assets/default.help.txt @@ -0,0 +1,108 @@ + +TOPIC + PowerShell Help System + +SHORT DESCRIPTION + Displays help about PowerShell cmdlets and concepts. + +LONG DESCRIPTION + PowerShell Help describes PowerShell cmdlets, functions, scripts, and + modules, and explains concepts, including the elements of the PowerShell + language. + + PowerShell does not include help files, but you can read the help topics + online, or use the Update-Help cmdlet to download help files to your + computer and then use the Get-Help cmdlet to display the help topics at + the command line. + + You can also use the Update-Help cmdlet to download updated help files + as they are released so that your local help content is never obsolete. + + Without help files, Get-Help displays auto-generated help for cmdlets, + functions, and scripts. + + + ONLINE HELP + You can find help for PowerShell online at + https://go.microsoft.com/fwlink/?LinkID=108518. + + To open online help for any cmdlet or function, type: + + Get-Help -Online + + UPDATE-HELP + To download and install help files on your computer: + + 1. Start PowerShell with the "Run as administrator" option. + 2. Type: + + Update-Help + + After the help files are installed, you can use the Get-Help cmdlet to + display the help topics. You can also use the Update-Help cmdlet to + download updated help files so that your local help files are always + up-to-date. + + For more information about the Update-Help cmdlet, type: + + Get-Help Update-Help -Online + + or go to: https://go.microsoft.com/fwlink/?LinkID=210614 + + + GET-HELP + The Get-Help cmdlet displays help at the command line from content in + help files on your computer. Without help files, Get-Help displays basic + help about cmdlets and functions. You can also use Get-Help to display + online help for cmdlets and functions. + + To get help for a cmdlet, type: + + Get-Help + + To get online help, type: + + Get-Help -Online + + The titles of conceptual topics begin with "About_". To get help for a + concept or language element, type: + + Get-Help About_ + + To search for a word or phrase in all help files, type: + + Get-Help + + For more information about the Get-Help cmdlet, type: + + Get-Help Get-Help -Online + + or go to: https://go.microsoft.com/fwlink/?LinkID=113316 + + + EXAMPLES: + Save-Help : Download help files from the internet and save + them on a file share. + + Update-Help : Downloads and installs help files from the + internet or a file share. + + Get-Help Get-Process : Displays help about the Get-Process cmdlet. + + Get-Help Get-Process -Online + : Opens online help for the Get-Process cmdlet. + + Help Get-Process : Displays help about Get-Process one page at a + time. + Get-Process -? : Displays help about the Get-Process cmdlet. + + Get-Help About_Modules : Displays help about PowerShell modules. + + Get-Help remoting : Searches the help topics for the word "remoting." + + SEE ALSO: + about_Updatable_Help + Get-Help + Save-Help + Update-Help + diff --git a/assets/files.wxs b/assets/files.wxs deleted file mode 100644 index cc24064ddb8..00000000000 --- a/assets/files.wxs +++ /dev/null @@ -1,4115 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/macos-entitlements.plist b/assets/macos-entitlements.plist new file mode 100644 index 00000000000..9d534f4f4bf --- /dev/null +++ b/assets/macos-entitlements.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/assets/manpage/pwsh.1 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 3472296251b..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/xml-documentation-comments). +### 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. @@ -111,9 +117,6 @@ Some general guidelines: * Avoid using string interpolations and overloads with implicit parameters such as `Culture` and `StringComparison`. Instead, use overloads with more explicit parameters such as `String.Format(IFormatProvider, String, Object[])` and `Equals(String, String, StringComparison)`. -* Avoid creating empty arrays. - Instead, reuse the static ones via `Utils.EmptyArray`. - * Avoid unnecessary memory allocation in a loop. Move the memory allocation outside the loop if possible. @@ -186,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 deleted file mode 100644 index 58f8d2c9b6d..00000000000 --- a/docs/host-powershell/sample/NuGet.config +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/docs/host-powershell/sample/NuGet.config.md b/docs/host-powershell/sample/NuGet.config.md new file mode 100644 index 00000000000..bf2b4c3f688 --- /dev/null +++ b/docs/host-powershell/sample/NuGet.config.md @@ -0,0 +1,16 @@ +# Nuget.config creation + +Create a filed called `nuget.config` at this location with this content: + +```xml + + + + + + + + + + +``` diff --git a/docs/learning-powershell/README.md b/docs/learning-powershell/README.md deleted file mode 100644 index 6ce2c8832ff..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-linux-and-macos-edition - -[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 ce49a4b9bfe..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.htm -[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 2cafeb94888..ebba4b02258 100644 --- a/docs/maintainers/README.md +++ b/docs/maintainers/README.md @@ -3,11 +3,11 @@ 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. -They have [write access](https://help.github.com/articles/repository-permission-levels-for-an-organization/) to the PowerShell repositories which gives them the power to: +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://help.github.com/articles/repository-permission- - [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://help.github.com/articles/repository-permission- -- 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..ccb4e5529d7 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, @@ -62,26 +57,29 @@ It **requires** that PowerShell Core has been built via `Start-PSBuild` from the #### Windows -The `Start-PSPackage` function delegates to `New-MSIPackage` which creates a Windows Installer Package of PowerShell. +`Start-PSPackage` supports creating ZIP and MSIX packages for Windows. +When called without `-Type` on Windows, it defaults to creating both ZIP and MSIX packages. The packages *must* be published in release mode, so make sure `-Configuration Release` is specified when running `Start-PSBuild`. -It uses the Windows Installer XML Toolset (WiX) to generate a MSI package, -which copies the output of the published PowerShell files to a version-specific folder in Program Files, -and installs a shortcut in the Start Menu. -It can be uninstalled through `Programs and Features`. - Note that PowerShell is always self-contained, thus using it does not require installing it. -The output of `Start-PSBuild` includes a `powershell.exe` executable which can simply be launched. +The output of `Start-PSBuild` includes a `pwsh.exe` executable which can simply be launched. #### Linux / macOS The `Start-PSPackage` function delegates to `New-UnixPackage`. -It relies on the [Effing Package Management][fpm] project, -which makes building packages for any (non-Windows) platform a breeze. -Similarly, the PowerShell man-page is generated from the Markdown-like file + +For **Linux** (Debian-based distributions), it relies on the [Effing Package Management][fpm] project, +which makes building packages a breeze. + +For **macOS**, it uses native packaging tools (`pkgbuild` and `productbuild`) from Xcode Command Line Tools, +eliminating the need for Ruby or fpm. + +For **Linux** (Red Hat-based distributions), it uses `rpmbuild` directly. + +The PowerShell man-page is generated from the Markdown-like file [`assets/pwsh.1.ronn`][man] using [Ronn][]. -The function `Start-PSBootstrap -Package` will install both these tools. +The function `Start-PSBootstrap -Package` will install these tools. To modify any property of the packages, edit the `New-UnixPackage` function. Please also refer to the function for details on the package properties @@ -104,7 +102,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. @@ -136,7 +134,7 @@ Without `-Name` specified, the primary `powershell` package will instead be created. [fpm]: https://github.com/jordansissel/fpm -[man]: ../../assets/pwsh.1.ronn +[man]: ../../assets/manpage/pwsh.1.ronn [ronn]: https://github.com/rtomayko/ronn ### Build and Packaging Examples @@ -167,13 +165,13 @@ Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration ```powershell # Create packages for v6.0.0-beta.1 release targeting Windows universal package. # 'win7-x64' / 'win7-x86' should be used for -WindowsRuntime. -Start-PSPackage -Type msi -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' Start-PSPackage -Type zip -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' +Start-PSPackage -Type msix -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' ``` ## NuGet Packages -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 +188,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 +213,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/docs/testing-guidelines/testing-guidelines.md b/docs/testing-guidelines/testing-guidelines.md index ce987beb6d0..e00c8352ee7 100755 --- a/docs/testing-guidelines/testing-guidelines.md +++ b/docs/testing-guidelines/testing-guidelines.md @@ -88,7 +88,7 @@ When you would want to do this: ### xUnit -For those tests which are not easily run via Pester, we have decided to use [xUnit](https://xunit.github.io/) as the test framework. +For those tests which are not easily run via Pester, we have decided to use [xUnit](https://xunit.net/) as the test framework. Currently, we have a minuscule number of tests which are run by using xUnit. ## Running tests outside of CI diff --git a/dsc/pwsh.profile.dsc.resource.json b/dsc/pwsh.profile.dsc.resource.json new file mode 100644 index 00000000000..aa5f5c29eee --- /dev/null +++ b/dsc/pwsh.profile.dsc.resource.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "description": "Manage PowerShell profiles.", + "tags": [ + "Linux", + "Windows", + "macOS", + "PowerShell" + ], + "type": "Microsoft.PowerShell/Profile", + "version": "0.1.0", + "get": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "get" + ], + "input": "stdin" + }, + "set": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "set" + ], + "input": "stdin" + }, + "export": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "export" + ], + "input": "stdin" + }, + "exitCodes": { + "0": "Success", + "1": "Error", + "2": "Input not supported for export operation" + }, + "schema": { + "embedded": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Profile", + "description": "Manage PowerShell profiles.", + "type": "object", + "unevaluatedProperties": false, + "required": [ + "profileType" + ], + "properties": { + "profileType": { + "type": "string", + "title": "Profile Type", + "description": "Defines which profile to manage. Valid values are: 'AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserAllHosts', and 'CurrentUserCurrentHost'.", + "enum": [ + "AllUsersCurrentHost", + "AllUsersAllHosts", + "CurrentUserAllHosts", + "CurrentUserCurrentHost" + ] + }, + "profilePath": { + "title": "Profile Path", + "description": "The full path to the profile file.", + "type": "string", + "readOnly": true + }, + "content": { + "title": "Content", + "description": "Defines the content of the profile. If you don't specify this property, the resource doesn't manage the file contents. If you specify this property as an empty string, the resource removes all content from the file. If you specify this property as a non-empty string, the resource sets the file contents to the specified string. The resources retains newlines from this property without any modification.", + "type": [ "string", "null" ] + }, + "_exist": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json" + }, + "_name": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json" + } + }, + "$defs": { + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json", + "title": "Instance should exist", + "description": "Indicates whether the DSC resource instance should exist.", + "type": "boolean", + "default": true, + "enum": [ + false, + true + ] + }, + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json", + "title": "Exported instance name", + "description": "Returns a generated name for the resource instance from an export operation.", + "readOnly": true, + "type": "string" + } + } + } + } +} diff --git a/dsc/pwsh.profile.resource.ps1 b/dsc/pwsh.profile.resource.ps1 new file mode 100644 index 00000000000..ad9cfa4a63a --- /dev/null +++ b/dsc/pwsh.profile.resource.ps1 @@ -0,0 +1,179 @@ +## Copyright (c) Microsoft Corporation. All rights reserved. +## Licensed under the MIT License. + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateSet('get', 'set', 'export')] + [string]$Operation, + [Parameter(ValueFromPipeline)] + [string[]]$UserInput +) + +Begin { + enum ProfileType { + AllUsersCurrentHost + AllUsersAllHosts + CurrentUserAllHosts + CurrentUserCurrentHost + } + + function New-PwshResource { + param( + [Parameter(Mandatory = $true)] + [ProfileType] $ProfileType, + + [Parameter(ParameterSetName = 'WithContent')] + [string] $Content, + + [Parameter(ParameterSetName = 'WithContent')] + [bool] $Exist + ) + + # Create the PSCustomObject with properties + $resource = [PSCustomObject]@{ + profileType = $ProfileType + content = $null + profilePath = GetProfilePath -profileType $ProfileType + _exist = $false + } + + # Add ToJson method + $resource | Add-Member -MemberType ScriptMethod -Name 'ToJson' -Value { + return ([ordered] @{ + profileType = $this.profileType + content = $this.content + profilePath = $this.profilePath + _exist = $this._exist + }) | ConvertTo-Json -Compress -EnumsAsStrings + } + + # Constructor logic - if Content and Exist parameters are provided (WithContent parameter set) + if ($PSCmdlet.ParameterSetName -eq 'WithContent') { + $resource.content = $Content + $resource._exist = $Exist + } else { + # Default constructor logic - read from file system + $fileExists = Test-Path $resource.profilePath + if ($fileExists) { + $resource.content = Get-Content -Path $resource.profilePath + } else { + $resource.content = $null + } + $resource._exist = $fileExists + } + + return $resource + } + + function GetProfilePath { + param ( + [ProfileType] $profileType + ) + + $path = switch ($profileType) { + 'AllUsersCurrentHost' { $PROFILE.AllUsersCurrentHost } + 'AllUsersAllHosts' { $PROFILE.AllUsersAllHosts } + 'CurrentUserAllHosts' { $PROFILE.CurrentUserAllHosts } + 'CurrentUserCurrentHost' { $PROFILE.CurrentUserCurrentHost } + } + + return $path + } + + function ExportOperation { + $allUserCurrentHost = New-PwshResource -ProfileType 'AllUsersCurrentHost' + $allUsersAllHost = New-PwshResource -ProfileType 'AllUsersAllHosts' + $currentUserAllHost = New-PwshResource -ProfileType 'CurrentUserAllHosts' + $currentUserCurrentHost = New-PwshResource -ProfileType 'CurrentUserCurrentHost' + + # Cannot use the ToJson() method here as we are adding a note property + $allUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $allUsersAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + } + + function GetOperation { + param ( + [Parameter(Mandatory = $true)] + $InputResource, + [Parameter()] + [switch] $AsJson + ) + + $profilePath = GetProfilePath -profileType $InputResource.profileType.ToString() + + $actualState = New-PwshResource -ProfileType $InputResource.profileType + + $actualState.profilePath = $profilePath + + $exists = Test-Path $profilePath + + if ($InputResource._exist -and $exists) { + $content = Get-Content -Path $profilePath + $actualState.Content = $content + } elseif ($InputResource._exist -and -not $exists) { + $actualState.Content = $null + $actualState._exist = $false + } elseif (-not $InputResource._exist -and $exists) { + $actualState.Content = Get-Content -Path $profilePath + $actualState._exist = $true + } else { + $actualState.Content = $null + $actualState._exist = $false + } + + if ($AsJson) { + return $actualState.ToJson() + } else { + return $actualState + } + } + + function SetOperation { + param ( + $InputResource + ) + + $actualState = GetOperation -InputResource $InputResource + + if ($InputResource._exist) { + if (-not $actualState._exist) { + $null = New-Item -Path $actualState.profilePath -ItemType File -Force + } + + if ($null -ne $InputResource.content) { + Set-Content -Path $actualState.profilePath -Value $InputResource.content + } + } elseif ($actualState._exist) { + Remove-Item -Path $actualState.profilePath -Force + } + } +} +End { + $inputJson = $input | ConvertFrom-Json + + if ($inputJson) { + $InputResource = New-PwshResource -ProfileType $inputJson.profileType -Content $inputJson.content -Exist $inputJson._exist + } + + switch ($Operation) { + 'get' { + GetOperation -InputResource $InputResource -AsJson + } + 'set' { + SetOperation -InputResource $InputResource + } + 'export' { + if ($inputJson) { + Write-Error "Input not supported for export operation" + exit 2 + } + + ExportOperation + } + } + + exit 0 +} diff --git a/es-metadata.yml b/es-metadata.yml new file mode 100644 index 00000000000..24da115c114 --- /dev/null +++ b/es-metadata.yml @@ -0,0 +1,12 @@ +schemaVersion: 1.0.0 +providers: +- provider: InventoryAsCode + version: 1.0.0 + metadata: + isProduction: true + accountableOwners: + service: cef1de07-99d6-45df-b907-77d0066032ec + routing: + defaultAreaPath: + org: msazure + path: One\MGMT\Compute\Powershell\Powershell\Powershell Core\pwsh diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json new file mode 100644 index 00000000000..31f7b965a5b --- /dev/null +++ b/experimental-feature-linux.json @@ -0,0 +1,9 @@ +[ + "PSFeedbackProvider", + "PSLoadAssemblyFromNativeCode", + "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", + "PSSerializeJSONLongEnumAsNumber", + "PSRedirectToVariable", + "PSSubsystemPluginModel" +] diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json new file mode 100644 index 00000000000..31f7b965a5b --- /dev/null +++ b/experimental-feature-windows.json @@ -0,0 +1,9 @@ +[ + "PSFeedbackProvider", + "PSLoadAssemblyFromNativeCode", + "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", + "PSSerializeJSONLongEnumAsNumber", + "PSRedirectToVariable", + "PSSubsystemPluginModel" +] diff --git a/global.json b/global.json index 54505d7ff9d..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "5.0.100-preview.7.20366.2" + "version": "11.0.100-preview.3.26207.106" } } diff --git a/nuget.config b/nuget.config index f6b2acb7d0c..388a65572dd 100644 --- a/nuget.config +++ b/nuget.config @@ -2,11 +2,9 @@ - - - - - - + + + + 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..8449c58ebb0 --- /dev/null +++ b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj @@ -0,0 +1,33 @@ + + + + Exe + net11.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 9cf9589326a..e794fd929d1 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Globalization; using System.Management.Automation; using System.Threading; @@ -27,9 +26,9 @@ internal abstract class CimAsyncOperation : IDisposable #region Constructor /// - /// The constructor. + /// Initializes a new instance of the class. /// - public CimAsyncOperation() + protected CimAsyncOperation() { this.moreActionEvent = new ManualResetEventSlim(false); this.actionQueue = new ConcurrentQueue(); @@ -207,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)) { @@ -238,7 +234,7 @@ protected bool IsActive() /// protected CimSessionProxy CreateCimSessionProxy(CimSessionProxy originalProxy) { - CimSessionProxy proxy = new CimSessionProxy(originalProxy); + CimSessionProxy proxy = new(originalProxy); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } @@ -260,7 +256,7 @@ protected CimSessionProxy CreateCimSessionProxy(CimSessionProxy originalProxy, b /// protected CimSessionProxy CreateCimSessionProxy(CimSession session) { - CimSessionProxy proxy = new CimSessionProxy(session); + CimSessionProxy proxy = new(session); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } @@ -283,7 +279,7 @@ protected CimSessionProxy CreateCimSessionProxy(CimSession session, bool passThr /// protected CimSessionProxy CreateCimSessionProxy(string computerName) { - CimSessionProxy proxy = new CimSessionProxy(computerName); + CimSessionProxy proxy = new(computerName); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } @@ -297,7 +293,7 @@ protected CimSessionProxy CreateCimSessionProxy(string computerName) /// protected CimSessionProxy CreateCimSessionProxy(string computerName, CimInstance cimInstance) { - CimSessionProxy proxy = new CimSessionProxy(computerName, cimInstance); + CimSessionProxy proxy = new(computerName, cimInstance); this.SubscribeEventAndAddProxytoCache(proxy); return proxy; } @@ -348,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; } @@ -384,12 +378,10 @@ 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); - CimInstance cimInstance = baseObject as CimInstance; - if (cimInstance == null) + if (baseObject is not CimInstance cimInstance) { return null; } @@ -399,12 +391,11 @@ 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; } - else if (!(cimReferenceArray[0] is PSReference)) + else if (cimReferenceArray[0] is not PSReference) { return null; } @@ -412,8 +403,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re CimInstance[] cimInstanceArray = new CimInstance[cimReferenceArray.Length]; for (int i = 0; i < cimReferenceArray.Length; i++) { - PSReference tempCimReference = cimReferenceArray[i] as PSReference; - if (tempCimReference == null) + if (cimReferenceArray[i] is not PSReference tempCimReference) { return null; } @@ -534,10 +524,7 @@ private void Cleanup() } this.moreActionEvent.Dispose(); - if (this.ackedEvent != null) - { - this.ackedEvent.Dispose(); - } + this.ackedEvent?.Dispose(); DebugHelper.WriteLog("Cleanup complete.", 2); } @@ -549,7 +536,7 @@ private void Cleanup() /// /// Lock object. /// - private readonly object a_lock = new object(); + private readonly object a_lock = new(); /// /// Number of active operations. @@ -559,19 +546,19 @@ private void Cleanup() /// /// Event to notify ps thread that more action is available. /// - private ManualResetEventSlim moreActionEvent; + private readonly ManualResetEventSlim moreActionEvent; /// /// The following is the definition of action queue. /// The queue holding all actions to be executed in the context of either /// ProcessRecord or EndProcessing. /// - private ConcurrentQueue actionQueue; + private readonly ConcurrentQueue actionQueue; /// /// Lock object. /// - private readonly object cimSessionProxyCacheLock = new object(); + private readonly object cimSessionProxyCacheLock = new(); /// /// Cache all objects related to diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs index 345b27bb48b..4702e47e2f2 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimBaseAction.cs @@ -3,9 +3,9 @@ #region Using directives +using System; using System.Threading; using Microsoft.Management.Infrastructure.Options; -using System; #endregion @@ -17,9 +17,9 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal abstract class CimBaseAction { /// - /// Constructor method. + /// Initializes a new instance of the class. /// - public CimBaseAction() + protected CimBaseAction() { } @@ -44,20 +44,7 @@ public virtual void Execute(CmdletOperationBase cmdlet) /// , object. /// /// - protected XOperationContextBase Context - { - get - { - return this.context; - } - - set - { - this.context = value; - } - } - - private XOperationContextBase context; + protected XOperationContextBase Context { get; set; } } /// @@ -69,7 +56,7 @@ protected XOperationContextBase Context internal class CimSyncAction : CimBaseAction, IDisposable { /// - /// The constructor. + /// Initializes a new instance of the class. /// public CimSyncAction() { @@ -126,7 +113,7 @@ protected virtual void Block() /// /// Action completed event. /// - private ManualResetEventSlim completeEvent; + private readonly ManualResetEventSlim completeEvent; /// /// Response result. @@ -181,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 2488490c514..89f7478b513 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimCommandBase.cs @@ -25,41 +25,25 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class ParameterDefinitionEntry { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// internal ParameterDefinitionEntry(string parameterSetName, bool mandatory) { - this.mandatory = mandatory; - this.parameterSetName = parameterSetName; + this.IsMandatory = mandatory; + this.ParameterSetName = parameterSetName; } /// /// Property ParameterSetName. /// - internal string ParameterSetName - { - get - { - return this.parameterSetName; - } - } - - private readonly string parameterSetName = null; + internal string ParameterSetName { get; } /// /// Whether the parameter is mandatory to the set. /// - internal bool IsMandatory - { - get - { - return this.mandatory; - } - } - - private readonly bool mandatory = false; + internal bool IsMandatory { get; } } /// @@ -70,36 +54,36 @@ internal bool IsMandatory internal class ParameterSetEntry { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// - internal ParameterSetEntry(UInt32 mandatoryParameterCount) + internal ParameterSetEntry(uint mandatoryParameterCount) { - this.mandatoryParameterCount = mandatoryParameterCount; - this.isDefaultParameterSet = false; + this.MandatoryParameterCount = mandatoryParameterCount; + this.IsDefaultParameterSet = false; reset(); } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// internal ParameterSetEntry(ParameterSetEntry toClone) { - this.mandatoryParameterCount = toClone.MandatoryParameterCount; - this.isDefaultParameterSet = toClone.IsDefaultParameterSet; + this.MandatoryParameterCount = toClone.MandatoryParameterCount; + this.IsDefaultParameterSet = toClone.IsDefaultParameterSet; reset(); } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// - internal ParameterSetEntry(UInt32 mandatoryParameterCount, bool isDefault) + internal ParameterSetEntry(uint mandatoryParameterCount, bool isDefault) { - this.mandatoryParameterCount = mandatoryParameterCount; - this.isDefaultParameterSet = isDefault; + this.MandatoryParameterCount = mandatoryParameterCount; + this.IsDefaultParameterSet = isDefault; reset(); } @@ -108,107 +92,39 @@ internal ParameterSetEntry(UInt32 mandatoryParameterCount, bool isDefault) /// internal void reset() { - this.setMandatoryParameterCount = this.setMandatoryParameterCountAtBeginProcess; - this.isValueSet = this.isValueSetAtBeginProcess; + this.SetMandatoryParameterCount = this.SetMandatoryParameterCountAtBeginProcess; + this.IsValueSet = this.IsValueSetAtBeginProcess; } /// /// Property DefaultParameterSet /// - internal bool IsDefaultParameterSet - { - get - { - return this.isDefaultParameterSet; - } - } - - private readonly bool isDefaultParameterSet = false; + internal bool IsDefaultParameterSet { get; } /// /// Property MandatoryParameterCount /// - internal UInt32 MandatoryParameterCount - { - get - { - return this.mandatoryParameterCount; - } - } - - private readonly UInt32 mandatoryParameterCount = 0; + internal uint MandatoryParameterCount { get; } = 0; /// /// Property IsValueSet /// - internal bool IsValueSet - { - get - { - return this.isValueSet; - } - - set - { - this.isValueSet = value; - } - } - - private bool isValueSet = false; + internal bool IsValueSet { get; set; } /// /// Property IsValueSetAtBeginProcess /// - internal bool IsValueSetAtBeginProcess - { - get - { - return this.isValueSetAtBeginProcess; - } - - set - { - this.isValueSetAtBeginProcess = value; - } - } - - private bool isValueSetAtBeginProcess = false; + internal bool IsValueSetAtBeginProcess { get; set; } /// /// Property SetMandatoryParameterCount /// - internal UInt32 SetMandatoryParameterCount - { - get - { - return this.setMandatoryParameterCount; - } - - set - { - this.setMandatoryParameterCount = value; - } - } - - private UInt32 setMandatoryParameterCount = 0; + internal uint SetMandatoryParameterCount { get; set; } = 0; /// /// Property SetMandatoryParameterCountAtBeginProcess /// - internal UInt32 SetMandatoryParameterCountAtBeginProcess - { - get - { - return this.setMandatoryParameterCountAtBeginProcess; - } - - set - { - this.setMandatoryParameterCountAtBeginProcess = value; - } - } - - private UInt32 setMandatoryParameterCountAtBeginProcess = 0; + internal uint SetMandatoryParameterCountAtBeginProcess { get; set; } = 0; } /// @@ -217,7 +133,7 @@ internal UInt32 SetMandatoryParameterCountAtBeginProcess internal class ParameterBinder { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -256,12 +172,12 @@ internal ParameterBinder( /// throw exception /// /// - private List parametersetNamesList = new List(); + private List parametersetNamesList = new(); /// /// Parameter names list. /// - private List parameterNamesList = new List(); + private readonly List parameterNamesList = new(); /// /// @@ -270,12 +186,12 @@ internal ParameterBinder( /// throw exception /// /// - private List parametersetNamesListAtBeginProcess = new List(); + private List parametersetNamesListAtBeginProcess = new(); /// /// Parameter names list before begin process. /// - private List parameterNamesListAtBeginProcess = new List(); + private readonly List parameterNamesListAtBeginProcess = new(); /// /// @@ -331,7 +247,7 @@ internal void SetParameter(string parameterName, bool isBeginProcess) if (this.parametersetNamesList.Count == 0) { - List nameset = new List(); + List nameset = new(); foreach (ParameterDefinitionEntry parameterDefinitionEntry in this.parameterDefinitionEntries[parameterName]) { DebugHelper.WriteLogEx("parameterset name = '{0}'; mandatory = '{1}'", 1, parameterDefinitionEntry.ParameterSetName, parameterDefinitionEntry.IsMandatory); @@ -370,7 +286,7 @@ internal void SetParameter(string parameterName, bool isBeginProcess) } else { - List nameset = new List(); + List nameset = new(); foreach (ParameterDefinitionEntry entry in this.parameterDefinitionEntries[parameterName]) { if (this.parametersetNamesList.Contains(entry.ParameterSetName)) @@ -418,7 +334,7 @@ internal string GetParameterSet() string boundParameterSetName = null; string defaultParameterSetName = null; - List noMandatoryParameterSet = new List(); + List noMandatoryParameterSet = new(); // Looking for parameter set which have mandatory parameters foreach (string parameterSetName in this.parameterSetEntries.Keys) @@ -474,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) @@ -531,7 +444,7 @@ internal void CheckParameterSet() { try { - this.parameterSetName = this.parameterBinder.GetParameterSet(); + this.ParameterSetName = this.parameterBinder.GetParameterSet(); } finally { @@ -539,7 +452,7 @@ internal void CheckParameterSet() } } - DebugHelper.WriteLog("current parameterset is: " + this.parameterSetName, 4); + DebugHelper.WriteLog("current parameterset is: " + this.ParameterSetName, 4); } /// @@ -557,17 +470,14 @@ 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 #region constructors /// - /// Constructor. + /// Initializes a new instance of the class. /// internal CimBaseCommand() { @@ -576,7 +486,7 @@ internal CimBaseCommand() } /// - /// Constructor. + /// Initializes a new instance of the class. /// internal CimBaseCommand(Dictionary> parameters, Dictionary sets) @@ -663,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 @@ -675,7 +582,7 @@ protected virtual void DisposeInternal() /// /// Parameter binder used to resolve parameter set name. /// - private ParameterBinder parameterBinder; + private readonly ParameterBinder parameterBinder; /// /// @@ -687,14 +594,7 @@ protected virtual void DisposeInternal() /// /// Lock object. /// - private readonly object myLock = new object(); - - /// - /// - /// parameter set name - /// - /// - private string parameterSetName; + private readonly object myLock = new(); /// /// This flag is introduced to resolve the parameter set name @@ -727,6 +627,11 @@ internal bool AtBeginProcess /// internal CimAsyncOperation AsyncOperation { + get + { + return this.operation; + } + set { lock (this.myLock) @@ -735,11 +640,6 @@ internal CimAsyncOperation AsyncOperation this.operation = value; } } - - get - { - return this.operation; - } } /// @@ -747,13 +647,7 @@ internal CimAsyncOperation AsyncOperation /// Get current ParameterSetName of the cmdlet /// /// - internal string ParameterSetName - { - get - { - return this.parameterSetName; - } - } + internal string ParameterSetName { get; private set; } /// /// Gets/Sets cmdlet operation wrapper object. @@ -769,9 +663,10 @@ internal virtual CmdletOperationBase CmdletOperation /// Throw terminating error /// /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(Exception exception, string operation) { - ErrorRecord errorRecord = new ErrorRecord(exception, operation, ErrorCategory.InvalidOperation, this); + ErrorRecord errorRecord = new(exception, operation, ErrorCategory.InvalidOperation, this); this.CmdletOperation.ThrowTerminatingError(errorRecord); } @@ -909,7 +804,7 @@ internal void ThrowInvalidAuthenticationTypeError( ImpersonatedAuthenticationMechanism.Negotiate, ImpersonatedAuthenticationMechanism.Kerberos, ImpersonatedAuthenticationMechanism.NtlmDomain); - PSArgumentOutOfRangeException exception = new PSArgumentOutOfRangeException( + PSArgumentOutOfRangeException exception = new( parameterName, authentication, message); ThrowTerminatingError(exception, operationName); } @@ -928,7 +823,7 @@ internal void ThrowConflictParameterWasSet( string message = string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.ConflictParameterWasSet, parameterName, conflictParameterName); - PSArgumentException exception = new PSArgumentException(message, parameterName); + PSArgumentException exception = new(message, parameterName); ThrowTerminatingError(exception, operationName); } @@ -944,12 +839,12 @@ internal void ThrowInvalidProperty( string operationName, IDictionary actualValue) { - StringBuilder propList = new StringBuilder(); + StringBuilder propList = new(); foreach (string property in propertiesList) { if (propList.Length > 0) { - propList.Append(","); + propList.Append(','); } propList.Append(property); @@ -957,7 +852,7 @@ internal void ThrowInvalidProperty( string message = string.Format(CultureInfo.CurrentUICulture, CimCmdletStrings.CouldNotFindPropertyFromGivenClass, className, propList); - PSArgumentOutOfRangeException exception = new PSArgumentOutOfRangeException( + PSArgumentOutOfRangeException exception = new( parameterName, actualValue, message); ThrowTerminatingError(exception, operationName); } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs index b0b95d14c50..0ab4988c57d 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetAssociatedInstance.cs @@ -3,8 +3,6 @@ #region Using directives -using System.Collections; -using System; using System.Collections.Generic; #endregion @@ -19,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal sealed class CimGetAssociatedInstance : CimAsyncOperation { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimGetAssociatedInstance() : base() @@ -45,7 +41,7 @@ public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) nameSpace = ConstValue.GetNamespace(cmdlet.CimInstance.CimSystemProperties.Namespace); } - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.ComputerSetName: @@ -89,7 +85,7 @@ public void GetCimAssociatedInstance(GetCimAssociatedInstanceCommand cmdlet) /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, GetCimAssociatedInstanceCommand cmdlet) { diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs index a3826487e24..c775325094b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetCimClass.cs @@ -3,11 +3,8 @@ #region Using directives -using System.Collections; -using System; using System.Collections.Generic; using System.Management.Automation; -using System.Globalization; #endregion @@ -20,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimGetCimClassContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -33,10 +28,10 @@ internal CimGetCimClassContext( string thePropertyName, string theQualifierName) { - this.className = theClassName; - this.methodName = theMethodName; - this.propertyName = thePropertyName; - this.qualifierName = theQualifierName; + this.ClassName = theClassName; + this.MethodName = theMethodName; + this.PropertyName = thePropertyName; + this.QualifierName = theQualifierName; } /// @@ -47,14 +42,7 @@ internal CimGetCimClassContext( /// Wildcard expansion should be allowed. /// /// - public string ClassName - { - get { return className; } - - set { className = value; } - } - - private string className; + public string ClassName { get; set; } /// /// @@ -63,12 +51,7 @@ public string ClassName /// Then Filter the by given methodname /// /// - internal string MethodName - { - get { return methodName; } - } - - private string methodName; + internal string MethodName { get; } /// /// @@ -77,12 +60,7 @@ internal string MethodName /// Filter the by given property name. /// /// - internal string PropertyName - { - get { return propertyName; } - } - - private string propertyName; + internal string PropertyName { get; } /// /// @@ -91,12 +69,7 @@ internal string PropertyName /// Filter the by given methodname /// /// - internal string QualifierName - { - get { return qualifierName; } - } - - private string qualifierName; + internal string QualifierName { get; } } /// @@ -107,9 +80,7 @@ internal string QualifierName internal sealed class CimGetCimClass : CimAsyncOperation { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimGetCimClass() : base() @@ -124,10 +95,10 @@ public CimGetCimClass() /// object. public void GetCimClass(GetCimClassCommand cmdlet) { - List proxys = new List(); + List proxys = new(); string nameSpace = ConstValue.GetNamespace(cmdlet.Namespace); - string className = (cmdlet.ClassName == null) ? @"*" : cmdlet.ClassName; - CimGetCimClassContext context = new CimGetCimClassContext( + string className = cmdlet.ClassName ?? @"*"; + CimGetCimClassContext context = new( cmdlet.ClassName, cmdlet.MethodName, cmdlet.PropertyName, @@ -189,11 +160,12 @@ public void GetCimClass(GetCimClassCommand cmdlet) /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, 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 c462d5b9918..371b06d9356 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimGetInstance.cs @@ -3,8 +3,6 @@ #region Using directives -using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Management.Automation; @@ -53,6 +51,7 @@ public object Process(object resultObject) internal class CimGetInstance : CimAsyncOperation { /// + /// Initializes a new instance of the class. /// /// Constructor /// @@ -83,8 +82,8 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) IEnumerable computerNames = ConstValue.GetComputerNames( GetComputerName(cmdlet)); string nameSpace; - List proxys = new List(); - bool isGetCimInstanceCommand = (cmdlet is GetCimInstanceCommand); + List proxys = new(); + bool isGetCimInstanceCommand = cmdlet is GetCimInstanceCommand; CimInstance targetCimInstance = null; switch (cmdlet.ParameterSetName) { @@ -95,7 +94,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) CimSessionProxy proxy = CreateSessionProxy(computerName, targetCimInstance, cmdlet); if (isGetCimInstanceCommand) { - this.SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); + SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); } proxys.Add(proxy); @@ -110,7 +109,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) CimSessionProxy proxy = CreateSessionProxy(computerName, cmdlet); if (isGetCimInstanceCommand) { - this.SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); + SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); } proxys.Add(proxy); @@ -126,7 +125,7 @@ protected void GetCimInstanceInternal(CimBaseCommand cmdlet) CimSessionProxy proxy = CreateSessionProxy(session, cmdlet); if (isGetCimInstanceCommand) { - this.SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); + SetPreProcess(proxy, cmdlet as GetCimInstanceCommand); } proxys.Add(proxy); @@ -285,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) { @@ -300,13 +298,12 @@ 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 StringBuilder(); + StringBuilder propertyList = new(); if (cmd.SelectProperties == null) { - propertyList.Append("*"); + propertyList.Append('*'); } else { @@ -314,7 +311,7 @@ protected static string CreateQuery(CimBaseCommand cmdlet) { if (propertyList.Length > 0) { - propertyList.Append(","); + propertyList.Append(','); } propertyList.Append(property); @@ -375,7 +372,7 @@ protected static CimInstance GetCimInstanceParameter(CimBaseCommand cmdlet) /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, CimBaseCommand cmdlet) { @@ -399,7 +396,7 @@ private void SetSessionProxyProperties( proxy.ResourceUri = removeCimInstance.ResourceUri; } - CimRemoveCimInstanceContext context = new CimRemoveCimInstanceContext( + CimRemoveCimInstanceContext context = new( ConstValue.GetNamespace(removeCimInstance.Namespace), proxy); proxy.ContextObject = context; @@ -413,7 +410,7 @@ private void SetSessionProxyProperties( proxy.ResourceUri = setCimInstance.ResourceUri; } - CimSetCimInstanceContext context = new CimSetCimInstanceContext( + CimSetCimInstanceContext context = new( ConstValue.GetNamespace(setCimInstance.Namespace), setCimInstance.Property, proxy, @@ -516,7 +513,7 @@ protected CimSessionProxy CreateSessionProxy( /// /// /// - private void SetPreProcess(CimSessionProxy proxy, GetCimInstanceCommand cmdlet) + private static void SetPreProcess(CimSessionProxy proxy, GetCimInstanceCommand cmdlet) { if (cmdlet.KeyOnly || (cmdlet.SelectProperties != null)) { diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs index fd44d922009..899e67495cc 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs @@ -45,26 +45,16 @@ public class CimIndicationEventExceptionEventArgs : CimIndicationEventArgs /// Returns an exception /// /// - public Exception Exception - { - get - { - return exception; - } - } - - private Exception exception; + public Exception Exception { get; } /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// public CimIndicationEventExceptionEventArgs(Exception theException) { context = null; - this.exception = theException; + this.Exception = theException; } } @@ -81,7 +71,7 @@ public CimInstance NewEvent { get { - return (result == null) ? null : result.Instance; + return result?.Instance; } } @@ -92,7 +82,7 @@ public string MachineId { get { - return (result == null) ? null : result.MachineId; + return result?.MachineId; } } @@ -103,14 +93,12 @@ public string Bookmark { get { - return (result == null) ? null : result.Bookmark; + return result?.Bookmark; } } /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// public CimIndicationEventInstanceEventArgs(CimSubscriptionResult result) @@ -124,7 +112,7 @@ public CimIndicationEventInstanceEventArgs(CimSubscriptionResult result) /// subscription result /// /// - private CimSubscriptionResult result; + private readonly CimSubscriptionResult result; } /// @@ -154,9 +142,7 @@ internal enum Status public event EventHandler CimIndicationArrived; /// - /// - /// Constructor with given computerName, namespace, queryExpression and timeout - /// + /// Initializes a new instance of the class. /// /// /// @@ -167,7 +153,7 @@ public CimIndicationWatcher( string theNamespace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { ValidationHelper.ValidateNoNullorWhiteSpaceArgument(queryExpression, queryExpressionParameterName); computerName = ConstValue.GetComputerName(computerName); @@ -176,9 +162,7 @@ public CimIndicationWatcher( } /// - /// - /// Constructor with given cimsession, namespace, queryExpression and timeout - /// + /// Initializes a new instance of the class. /// /// /// @@ -189,7 +173,7 @@ public CimIndicationWatcher( string theNamespace, string queryDialect, string queryExpression, - UInt32 operationTimeout) + uint operationTimeout) { ValidationHelper.ValidateNoNullorWhiteSpaceArgument(queryExpression, queryExpressionParameterName); ValidationHelper.ValidateNoNullArgument(cimSession, cimSessionParameterName); @@ -208,7 +192,7 @@ private void Initialize( string theNameSpace, string theQueryDialect, string theQueryExpression, - UInt32 theOperationTimeout) + uint theOperationTimeout) { enableRaisingEvents = false; status = Status.Default; @@ -237,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)); } } } @@ -258,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 @@ -394,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/CimInvokeCimMethod.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs index ba82ff9e432..50f4d12dd74 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimInvokeCimMethod.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Management.Automation; #endregion @@ -28,9 +27,7 @@ internal sealed class CimInvokeCimMethod : CimAsyncOperation internal class CimInvokeCimMethodContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -41,42 +38,24 @@ internal CimInvokeCimMethodContext(string theNamespace, CimSessionProxy theProxy) { this.proxy = theProxy; - this.methodName = theMethodName; - this.collection = theCollection; + this.MethodName = theMethodName; + this.ParametersCollection = theCollection; this.nameSpace = theNamespace; } /// /// namespace /// - internal string MethodName - { - get - { - return this.methodName; - } - } - - private string methodName; + internal string MethodName { get; } /// /// parameters collection /// - internal CimMethodParametersCollection ParametersCollection - { - get - { - return this.collection; - } - } - - private CimMethodParametersCollection collection; + internal CimMethodParametersCollection ParametersCollection { get; } } /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimInvokeCimMethod() : base() @@ -93,7 +72,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) { IEnumerable computerNames = ConstValue.GetComputerNames(cmdlet.ComputerName); string nameSpace; - List proxys = new List(); + List proxys = new(); string action = string.Format(CultureInfo.CurrentUICulture, actionTemplate, cmdlet.MethodName); switch (cmdlet.ParameterSetName) @@ -195,7 +174,7 @@ public void InvokeCimMethod(InvokeCimMethodCommand cmdlet) foreach (CimSessionProxy proxy in proxys) { // create context object - CimInvokeCimMethodContext context = new CimInvokeCimMethodContext( + CimInvokeCimMethodContext context = new( nameSpace, cmdlet.MethodName, paramsCollection, @@ -275,7 +254,7 @@ public void InvokeCimMethodOnCimInstance(CimInstance cimInstance, XOperationCont /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, InvokeCimMethodCommand cmdlet) { @@ -374,7 +353,7 @@ private CimMethodParametersCollection CreateParametersCollection( { string parameterName = enumerator.Key.ToString(); - CimFlags parameterFlags = CimFlags.In; + const CimFlags parameterFlags = CimFlags.In; object parameterValue = GetBaseObject(enumerator.Value); DebugHelper.WriteLog(@"Create parameter name= {0}, value= {1}, flags= {2}.", 4, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs index 14c57c87888..beb3e551d33 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimNewCimInstance.cs @@ -20,9 +20,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimNewCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -44,6 +42,7 @@ internal CimNewCimInstanceContext( internal sealed class CimNewCimInstance : CimAsyncOperation { /// + /// Initializes a new instance of the class. /// /// Constructor /// @@ -133,7 +132,7 @@ public void NewCimInstance(NewCimInstanceCommand cmdlet) } // create ciminstance on server - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { @@ -180,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); } @@ -203,7 +201,7 @@ internal void GetCimInstance(CimInstance cimInstance, XOperationContextBase cont /// /// /// - private void SetSessionProxyProperties( + private static void SetSessionProxyProperties( ref CimSessionProxy proxy, NewCimInstanceCommand cmdlet) { @@ -267,13 +265,13 @@ private CimInstance CreateCimInstance( IDictionary properties, NewCimInstanceCommand cmdlet) { - CimInstance cimInstance = new CimInstance(className, cimNamespace); + CimInstance cimInstance = new(className, cimNamespace); if (properties == null) { return cimInstance; } - List keys = new List(); + List keys = new(); if (key != null) { foreach (string keyName in key) @@ -296,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); @@ -331,13 +328,13 @@ private CimInstance CreateCimInstance( IDictionary properties, NewCimInstanceCommand cmdlet) { - CimInstance cimInstance = new CimInstance(cimClass); + CimInstance cimInstance = new(cimClass); if (properties == null) { return cimInstance; } - List notfoundProperties = new List(); + List notfoundProperties = new(); foreach (string property in properties.Keys) { if (cimInstance.CimInstanceProperties[property] == null) diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs index 4d4ff5d6ec5..48b887f00b4 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimPromptUser.cs @@ -22,12 +22,12 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal sealed class CimPromptUser : CimSyncAction { /// - /// Constructor. + /// Initializes a new instance of the class. /// public CimPromptUser(string message, CimPromptType prompt) { - this.message = message; + this.Message = message; this.prompt = prompt; } @@ -54,7 +54,7 @@ public override void Execute(CmdletOperationBase cmdlet) // NOTES: prepare the whatif message and caption try { - result = cmdlet.ShouldContinue(message, "caption", ref yestoall, ref notoall); + result = cmdlet.ShouldContinue(Message, "caption", ref yestoall, ref notoall); if (yestoall) { this.responseType = CimResponseType.YesToAll; @@ -87,7 +87,7 @@ public override void Execute(CmdletOperationBase cmdlet) case CimPromptType.Normal: try { - result = cmdlet.ShouldProcess(message); + result = cmdlet.ShouldProcess(Message); if (result) { this.responseType = CimResponseType.Yes; @@ -121,20 +121,12 @@ public override void Execute(CmdletOperationBase cmdlet) /// /// Prompt message. /// - public string Message - { - get - { - return message; - } - } - - private string message; + public string Message { get; } /// /// Prompt type -Normal or Critical. /// - private CimPromptType prompt; + private readonly CimPromptType prompt; #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs index 559c58b8a11..692a5f123e8 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRegisterCimIndication.cs @@ -47,25 +47,17 @@ internal class CimSubscriptionResultEventArgs : CimSubscriptionEventArgs /// subscription result /// /// - public CimSubscriptionResult Result - { - get - { - return result; - } - } - - private CimSubscriptionResult result; + public CimSubscriptionResult Result { get; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// public CimSubscriptionResultEventArgs( CimSubscriptionResult theResult) { this.context = null; - this.result = theResult; + this.Result = theResult; } } @@ -81,25 +73,17 @@ internal class CimSubscriptionExceptionEventArgs : CimSubscriptionEventArgs /// subscription result /// /// - public Exception Exception - { - get - { - return exception; - } - } - - private Exception exception; + public Exception Exception { get; } /// - /// Constructor + /// Initializes a new instance of the class. /// /// public CimSubscriptionExceptionEventArgs( Exception theException) { this.context = null; - this.exception = theException; + this.Exception = theException; } } @@ -118,9 +102,7 @@ internal sealed class CimRegisterCimIndication : CimAsyncOperation public event EventHandler OnNewSubscriptionResult; /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimRegisterCimIndication() : base() @@ -141,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; @@ -164,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); @@ -216,10 +196,9 @@ 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; + this.Exception = cimWriteError.Exception; if (!this.ackedEvent.IsSet) { // an exception happened @@ -233,17 +212,15 @@ private void CimIndicationHandler(object cimSession, CmdletActionEventArgs actio { DebugHelper.WriteLog("Raise an exception event", 2); - temp(this, new CimSubscriptionExceptionEventArgs(this.exception)); + temp(this, new CimSubscriptionExceptionEventArgs(this.Exception)); } - DebugHelper.WriteLog("Got an exception: {0}", 2, exception); + 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) @@ -276,7 +253,7 @@ private void WaitForAckMessage() { DebugHelper.WriteLogEx(); this.ackedEvent.Wait(); - if (this.exception != null) + if (this.Exception != null) { DebugHelper.WriteLogEx("error happened", 0); if (this.Cmdlet != null) @@ -285,14 +262,14 @@ private void WaitForAckMessage() // throw terminating error ErrorRecord errorRecord = ErrorToErrorRecord.ErrorRecordFromAnyException( - new InvocationContext(this.TargetComputerName, null), this.exception, null); + new InvocationContext(this.TargetComputerName, null), this.Exception, null); this.Cmdlet.ThrowTerminatingError(errorRecord); } else { DebugHelper.WriteLogEx("Throw exception", 1); // throw exception out - throw this.exception; + throw this.Exception; } } @@ -309,8 +286,8 @@ private void WaitForAckMessage() /// internal Cmdlet Cmdlet { - set; get; + set; } /// @@ -318,8 +295,8 @@ internal Cmdlet Cmdlet /// internal string TargetComputerName { - set; get; + set; } #endregion @@ -335,7 +312,7 @@ internal string TargetComputerName /// private CimSessionProxy CreateSessionProxy( string computerName, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(computerName); proxy.OperationTimeout = timeout; @@ -350,7 +327,7 @@ private CimSessionProxy CreateSessionProxy( /// private CimSessionProxy CreateSessionProxy( CimSession session, - UInt32 timeout) + uint timeout) { CimSessionProxy proxy = CreateCimSessionProxy(session); proxy.OperationTimeout = timeout; @@ -363,15 +340,7 @@ private CimSessionProxy CreateSessionProxy( /// /// Exception occurred while start the subscription. /// - internal Exception Exception - { - get - { - return exception; - } - } - - private Exception exception; + internal Exception Exception { get; private set; } #endregion diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs index 3dbae04b726..a6182166170 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimRemoveCimInstance.cs @@ -3,8 +3,6 @@ #region Using directives -using System.Collections; -using System; using System.Collections.Generic; using System.Diagnostics; @@ -19,9 +17,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimRemoveCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -41,9 +37,7 @@ internal CimRemoveCimInstanceContext(string theNamespace, internal sealed class CimRemoveCimInstance : CimGetInstance { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimRemoveCimInstance() : base() @@ -62,7 +56,7 @@ public void RemoveCimInstance(RemoveCimInstanceCommand cmdlet) IEnumerable computerNames = ConstValue.GetComputerNames( GetComputerName(cmdlet)); - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs index 55cf540c0e8..389c45c8314 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimResultObserver.cs @@ -4,8 +4,8 @@ #region Using directives using System; -using System.Management.Automation; using System.Globalization; +using System.Management.Automation; #endregion @@ -32,26 +32,18 @@ public enum AsyncResultType internal class CimResultContext { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// internal CimResultContext(object ErrorSource) { - this.errorSource = ErrorSource; + this.ErrorSource = ErrorSource; } /// /// ErrorSource property. /// - internal object ErrorSource - { - get - { - return this.errorSource; - } - } - - private object errorSource; + internal object ErrorSource { get; } } #endregion @@ -64,12 +56,12 @@ internal object ErrorSource internal abstract class AsyncResultEventArgsBase : EventArgs { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// /// - public AsyncResultEventArgsBase( + protected AsyncResultEventArgsBase( CimSession session, IObservable observable, AsyncResultType resultType) @@ -80,13 +72,13 @@ public AsyncResultEventArgsBase( } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// /// /// - public AsyncResultEventArgsBase( + protected AsyncResultEventArgsBase( CimSession session, IObservable observable, AsyncResultType resultType, @@ -117,16 +109,14 @@ public AsyncResultEventArgsBase( internal class AsyncResultCompleteEventArgs : AsyncResultEventArgsBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// object. /// public AsyncResultCompleteEventArgs( CimSession session, - IObservable observable) : - base(session, observable, AsyncResultType.Completion) + IObservable observable) + : base(session, observable, AsyncResultType.Completion) { } } @@ -139,7 +129,7 @@ public AsyncResultCompleteEventArgs( internal class AsyncResultObjectEventArgs : AsyncResultEventArgsBase { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -147,8 +137,8 @@ internal class AsyncResultObjectEventArgs : AsyncResultEventArgsBase public AsyncResultObjectEventArgs( CimSession session, IObservable observable, - object resultObject) : - base(session, observable, AsyncResultType.Result) + object resultObject) + : base(session, observable, AsyncResultType.Result) { this.resultObject = resultObject; } @@ -164,7 +154,7 @@ public AsyncResultObjectEventArgs( internal class AsyncResultErrorEventArgs : AsyncResultEventArgsBase { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -172,14 +162,14 @@ internal class AsyncResultErrorEventArgs : AsyncResultEventArgsBase public AsyncResultErrorEventArgs( CimSession session, IObservable observable, - Exception error) : - base(session, observable, AsyncResultType.Exception) + Exception error) + : base(session, observable, AsyncResultType.Exception) { this.error = error; } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -189,8 +179,8 @@ public AsyncResultErrorEventArgs( CimSession session, IObservable observable, Exception error, - CimResultContext cimResultContext) : - base(session, observable, AsyncResultType.Exception, cimResultContext) + CimResultContext cimResultContext) + : base(session, observable, AsyncResultType.Exception, cimResultContext) { this.error = error; } @@ -216,34 +206,24 @@ public AsyncResultErrorEventArgs( /// object type internal class CimResultObserver : IObserver { - /// - /// Define delegate that handles new cmdlet action come from - /// the operations related to the current CimSession object. - /// - /// CimSession object, which raised the event. - /// Event args. - public delegate void ResultEventHandler( - object observer, - AsyncResultEventArgsBase resultArgs); - /// /// Define an Event based on the NewActionHandler. /// - public event ResultEventHandler OnNewResult; + public event EventHandler OnNewResult; /// - /// Constructor. + /// Initializes a new instance of the class. /// /// object that issued the operation. /// Operation that can be observed. public CimResultObserver(CimSession session, IObservable observable) { - this.session = session; + this.CurrentSession = session; this.observable = observable; } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// object that issued the operation. /// Operation that can be observed. @@ -251,7 +231,7 @@ public CimResultObserver(CimSession session, IObservable observable, CimResultContext cimResultContext) { - this.session = session; + this.CurrentSession = session; this.observable = observable; this.context = cimResultContext; } @@ -269,8 +249,8 @@ public virtual void OnCompleted() // OnNext, OnError try { - AsyncResultCompleteEventArgs completeArgs = new AsyncResultCompleteEventArgs( - this.session, this.observable); + AsyncResultCompleteEventArgs completeArgs = new( + this.CurrentSession, this.observable); this.OnNewResult(this, completeArgs); } catch (Exception ex) @@ -290,8 +270,8 @@ public virtual void OnError(Exception error) { try { - AsyncResultErrorEventArgs errorArgs = new AsyncResultErrorEventArgs( - this.session, this.observable, error, this.context); + AsyncResultErrorEventArgs errorArgs = new( + this.CurrentSession, this.observable, error, this.context); this.OnNewResult(this, errorArgs); } catch (Exception ex) @@ -310,8 +290,8 @@ protected void OnNextCore(object value) DebugHelper.WriteLogEx("value = {0}.", 1, value); try { - AsyncResultObjectEventArgs resultArgs = new AsyncResultObjectEventArgs( - this.session, this.observable, value); + AsyncResultObjectEventArgs resultArgs = new( + this.CurrentSession, this.observable, value); this.OnNewResult(this, resultArgs); } catch (Exception ex) @@ -344,25 +324,17 @@ public virtual void OnNext(T value) /// /// Session object of the operation. /// - protected CimSession CurrentSession - { - get - { - return session; - } - } - - private CimSession session; + protected CimSession CurrentSession { get; } /// /// Async operation that can be observed. /// - private IObservable observable; + private readonly IObservable observable; /// /// object used during delivering result. /// - private CimResultContext context; + private readonly CimResultContext context; #endregion } @@ -372,7 +344,7 @@ protected CimSession CurrentSession internal class CimSubscriptionResultObserver : CimResultObserver { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -382,7 +354,7 @@ public CimSubscriptionResultObserver(CimSession session, IObservable obs } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -411,7 +383,7 @@ public override void OnNext(CimSubscriptionResult value) internal class CimMethodResultObserver : CimResultObserver { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -421,7 +393,7 @@ public CimMethodResultObserver(CimSession session, IObservable observabl } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// @@ -447,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(); @@ -459,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(); @@ -504,7 +474,7 @@ internal string ClassName internal class IgnoreResultObserver : CimResultObserver { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs index 0dd85ea213c..b8ebc9adaef 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionOperations.cs @@ -6,9 +6,9 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Globalization; using Microsoft.Management.Infrastructure.Options; #endregion @@ -24,67 +24,27 @@ internal class CimSessionWrapper /// /// Id of the cimsession. /// - public uint SessionId - { - get - { - return this.sessionId; - } - } - - private uint sessionId; + public uint SessionId { get; } /// /// InstanceId of the cimsession. /// - public Guid InstanceId - { - get - { - return this.instanceId; - } - } - - private Guid instanceId; + public Guid InstanceId { get; } /// /// Name of the cimsession. /// - public string Name - { - get - { - return this.name; - } - } - - private string name; + public string Name { get; } /// /// Computer name of the cimsession. /// - public string ComputerName - { - get - { - return this.computerName; - } - } - - private string computerName; + public string ComputerName { get; } /// /// Wrapped cimsession object. /// - public CimSession CimSession - { - get - { - return this.cimSession; - } - } - - private CimSession cimSession; + public CimSession CimSession { get; } /// /// Computer name of the cimsession. @@ -110,7 +70,7 @@ internal ProtocolType GetProtocolType() return protocol; } - private ProtocolType protocol; + private readonly ProtocolType protocol; /// /// PSObject that wrapped the cimSession. @@ -127,11 +87,11 @@ internal CimSessionWrapper( CimSession theCimSession, ProtocolType theProtocol) { - this.sessionId = theSessionId; - this.instanceId = theInstanceId; - this.name = theName; - this.computerName = theComputerName; - this.cimSession = theCimSession; + this.SessionId = theSessionId; + this.InstanceId = theInstanceId; + this.Name = theName; + this.ComputerName = theComputerName; + this.CimSession = theCimSession; this.psObject = null; this.protocol = theProtocol; } @@ -140,18 +100,18 @@ internal PSObject GetPSObject() { if (psObject == null) { - psObject = new PSObject(this.cimSession); - psObject.Properties.Add(new PSNoteProperty(CimSessionState.idPropName, this.sessionId)); - psObject.Properties.Add(new PSNoteProperty(CimSessionState.namePropName, this.name)); - psObject.Properties.Add(new PSNoteProperty(CimSessionState.instanceidPropName, this.instanceId)); + psObject = new PSObject(this.CimSession); + psObject.Properties.Add(new PSNoteProperty(CimSessionState.idPropName, this.SessionId)); + psObject.Properties.Add(new PSNoteProperty(CimSessionState.namePropName, this.Name)); + psObject.Properties.Add(new PSNoteProperty(CimSessionState.instanceidPropName, this.InstanceId)); psObject.Properties.Add(new PSNoteProperty(CimSessionState.computernamePropName, this.ComputerName)); psObject.Properties.Add(new PSNoteProperty(CimSessionState.protocolPropName, this.Protocol)); } else { psObject.Properties[CimSessionState.idPropName].Value = this.SessionId; - psObject.Properties[CimSessionState.namePropName].Value = this.name; - psObject.Properties[CimSessionState.instanceidPropName].Value = this.instanceId; + psObject.Properties[CimSessionState.namePropName].Value = this.Name; + psObject.Properties[CimSessionState.instanceidPropName].Value = this.InstanceId; psObject.Properties[CimSessionState.computernamePropName].Value = this.ComputerName; psObject.Properties[CimSessionState.protocolPropName].Value = this.Protocol; } @@ -231,42 +191,40 @@ internal class CimSessionState : IDisposable /// Dictionary used to holds all CimSessions in current runspace by session name. /// /// - private Dictionary> curCimSessionsByName; + private readonly Dictionary> curCimSessionsByName; /// /// /// Dictionary used to holds all CimSessions in current runspace by computer name. /// /// - private Dictionary> curCimSessionsByComputerName; + private readonly Dictionary> curCimSessionsByComputerName; /// /// /// Dictionary used to holds all CimSessions in current runspace by instance ID. /// /// - private Dictionary curCimSessionsByInstanceId; + private readonly Dictionary curCimSessionsByInstanceId; /// /// /// Dictionary used to holds all CimSessions in current runspace by session id. /// /// - private Dictionary curCimSessionsById; + private readonly Dictionary curCimSessionsById; /// /// /// Dictionary used to link CimSession object with PSObject. /// /// - private Dictionary curCimSessionWrapper; + private readonly Dictionary curCimSessionWrapper; #endregion /// - /// - /// The constructor. - /// + /// Initializes a new instance of the class. /// internal CimSessionState() { @@ -399,7 +357,7 @@ internal PSObject AddObjectToCache( string computerName, ProtocolType protocol) { - CimSessionWrapper wrapper = new CimSessionWrapper( + CimSessionWrapper wrapper = new( sessionId, instanceId, name, computerName, session, protocol); HashSet objects; @@ -546,7 +504,7 @@ private void RemoveSessionInternal(CimSession session, CimSessionWrapper wrapper /// /// /// - private void AddErrorRecord( + private static void AddErrorRecord( ref List errRecords, string propertyName, object propertyValue) @@ -568,18 +526,17 @@ internal IEnumerable QuerySession( IEnumerable ids, out IEnumerable errorRecords) { - HashSet sessions = new HashSet(); - HashSet sessionIds = new HashSet(); - List errRecords = new List(); + HashSet sessions = new(); + HashSet sessionIds = new(); + List errRecords = new(); errorRecords = errRecords; // NOTES: use template function to implement this will save duplicate code foreach (uint id in ids) { if (this.curCimSessionsById.ContainsKey(id)) { - if (!sessionIds.Contains(id)) + if (sessionIds.Add(id)) { - sessionIds.Add(id); sessions.Add(this.curCimSessionsById[id].GetPSObject()); } } @@ -601,9 +558,9 @@ internal IEnumerable QuerySession( IEnumerable instanceIds, out IEnumerable errorRecords) { - HashSet sessions = new HashSet(); - HashSet sessionIds = new HashSet(); - List errRecords = new List(); + HashSet sessions = new(); + HashSet sessionIds = new(); + List errRecords = new(); errorRecords = errRecords; foreach (Guid instanceid in instanceIds) { @@ -633,20 +590,20 @@ internal IEnumerable QuerySession( internal IEnumerable QuerySession(IEnumerable nameArray, out IEnumerable errorRecords) { - HashSet sessions = new HashSet(); - HashSet sessionIds = new HashSet(); - List errRecords = new List(); + HashSet sessions = new(); + HashSet sessionIds = new(); + List errRecords = new(); errorRecords = errRecords; foreach (string name in nameArray) { bool foundSession = false; - WildcardPattern pattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); + WildcardPattern pattern = new(name, WildcardOptions.IgnoreCase); foreach (KeyValuePair> kvp in this.curCimSessionsByName) { if (pattern.IsMatch(kvp.Key)) { HashSet wrappers = kvp.Value; - foundSession = (wrappers.Count > 0); + foundSession = wrappers.Count > 0; foreach (CimSessionWrapper wrapper in wrappers) { if (!sessionIds.Contains(wrapper.SessionId)) @@ -676,9 +633,9 @@ internal IEnumerable QuerySessionByComputerName( IEnumerable computernameArray, out IEnumerable errorRecords) { - HashSet sessions = new HashSet(); - HashSet sessionIds = new HashSet(); - List errRecords = new List(); + HashSet sessions = new(); + HashSet sessionIds = new(); + List errRecords = new(); errorRecords = errRecords; foreach (string computername in computernameArray) { @@ -686,7 +643,7 @@ internal IEnumerable QuerySessionByComputerName( if (this.curCimSessionsByComputerName.ContainsKey(computername)) { HashSet wrappers = this.curCimSessionsByComputerName[computername]; - foundSession = (wrappers.Count > 0); + foundSession = wrappers.Count > 0; foreach (CimSessionWrapper wrapper in wrappers) { if (!sessionIds.Contains(wrapper.SessionId)) @@ -714,9 +671,9 @@ internal IEnumerable QuerySessionByComputerName( internal IEnumerable QuerySession(IEnumerable cimsessions, out IEnumerable errorRecords) { - HashSet sessions = new HashSet(); - HashSet sessionIds = new HashSet(); - List errRecords = new List(); + HashSet sessions = new(); + HashSet sessionIds = new(); + List errRecords = new(); errorRecords = errRecords; foreach (CimSession cimsession in cimsessions) { @@ -785,13 +742,13 @@ internal class CimSessionBase #region constructor /// - /// The constructor. + /// Initializes a new instance of the class. /// public CimSessionBase() { this.sessionState = cimSessions.GetOrAdd( CurrentRunspaceId, - delegate (Guid instanceId) + (Guid instanceId) => { if (Runspace.DefaultRunspace != null) { @@ -814,7 +771,7 @@ public CimSessionBase() /// /// internal static readonly ConcurrentDictionary cimSessions - = new ConcurrentDictionary(); + = new(); /// /// @@ -908,9 +865,7 @@ internal class CimNewSession : CimSessionBase, IDisposable internal class CimTestCimSessionContext : XOperationContextBase { /// - /// - /// The constructor. - /// + /// Initializes a new instance of the class. /// /// /// @@ -919,33 +874,23 @@ internal CimTestCimSessionContext( CimSessionWrapper wrapper) { this.proxy = theProxy; - this.cimSessionWrapper = wrapper; + this.CimSessionWrapper = wrapper; this.nameSpace = null; } /// /// Namespace /// - internal CimSessionWrapper CimSessionWrapper - { - get - { - return this.cimSessionWrapper; - } - } - - private CimSessionWrapper cimSessionWrapper; + internal CimSessionWrapper CimSessionWrapper { get; } } /// - /// - /// The constructor. - /// + /// Initializes a new instance of the class. /// internal CimNewSession() : base() { this.cimTestSession = new CimTestSession(); - this._disposed = false; + this.Disposed = false; } /// @@ -974,8 +919,8 @@ internal void NewCimSession(NewCimSessionCommand cmdlet, proxy = new CimSessionProxyTestConnection(computerName, sessionOptions); string computerNameValue = (computerName == ConstValue.NullComputerName) ? ConstValue.LocalhostComputerName : computerName; - CimSessionWrapper wrapper = new CimSessionWrapper(0, Guid.Empty, cmdlet.Name, computerNameValue, proxy.CimSession, proxy.Protocol); - CimTestCimSessionContext context = new CimTestCimSessionContext(proxy, wrapper); + CimSessionWrapper wrapper = new(0, Guid.Empty, cmdlet.Name, computerNameValue, proxy.CimSession, proxy.Protocol); + CimTestCimSessionContext context = new(proxy, wrapper); proxy.ContextObject = context; // Skip test the connection if user intend to if (cmdlet.SkipTestConnection.IsPresent) @@ -1005,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(); @@ -1052,7 +997,7 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) /// object. /// /// - private CimTestSession cimTestSession; + private readonly CimTestSession cimTestSession; #endregion // private members #region IDisposable @@ -1062,15 +1007,7 @@ public void ProcessRemainActions(CmdletOperationBase cmdletOperation) /// Indicates whether this object was disposed or not. /// /// - protected bool Disposed - { - get - { - return _disposed; - } - } - - private bool _disposed; + protected bool Disposed { get; private set; } /// /// @@ -1104,13 +1041,13 @@ public void Dispose() /// Whether it is directly called. protected virtual void Dispose(bool disposing) { - if (!this._disposed) + if (!this.Disposed) { if (disposing) { // free managed resources this.cimTestSession.Dispose(); - this._disposed = true; + this.Disposed = true; } // free native resources if there are any } @@ -1130,7 +1067,7 @@ protected virtual void Dispose(bool disposing) internal class CimGetSession : CimSessionBase { /// - /// The constructor. + /// Initializes a new instance of the class. /// public CimGetSession() : base() { @@ -1212,7 +1149,7 @@ internal class CimRemoveSession : CimSessionBase internal static readonly string RemoveCimSessionActionName = "Remove CimSession"; /// - /// Constructor. + /// Initializes a new instance of the class. /// public CimRemoveSession() : base() { @@ -1282,7 +1219,7 @@ public void RemoveCimSession(RemoveCimSessionCommand cmdlet) internal class CimTestSession : CimAsyncOperation { /// - /// Constructor. + /// Initializes a new instance of the class. /// internal CimTestSession() : base() diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs index 084bad51d91..5cdc168ae24 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs @@ -65,7 +65,7 @@ internal CimSessionProxy Proxy internal class InvocationContext { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// internal InvocationContext(CimSessionProxy proxy) @@ -78,7 +78,7 @@ internal InvocationContext(CimSessionProxy proxy) } /// - /// Constructor. + /// Initializes a new instance of the class. /// /// internal InvocationContext(string computerName, CimInstance targetCimInstance) @@ -91,29 +91,21 @@ internal InvocationContext(string computerName, CimInstance targetCimInstance) /// /// ComputerName of the session /// + /// /// /// return value could be null /// - /// - internal virtual string ComputerName - { - get; - private set; - } + internal virtual string ComputerName { get; } /// /// /// CimInstance on which the current operation against. /// + /// /// /// return value could be null /// - /// - internal virtual CimInstance TargetCimInstance - { - get; - private set; - } + internal virtual CimInstance TargetCimInstance { get; } } #endregion @@ -143,7 +135,7 @@ internal interface IObjectPreProcess internal sealed class CmdletActionEventArgs : EventArgs { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// CimBaseAction object bound to the event. public CmdletActionEventArgs(CimBaseAction action) @@ -160,7 +152,7 @@ public CmdletActionEventArgs(CimBaseAction action) internal sealed class OperationEventArgs : EventArgs { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// Object used to cancel the operation. /// Async observable operation. @@ -200,7 +192,7 @@ internal class CimSessionProxy : IDisposable /// /// Temporary CimSession cache lock. /// - private static readonly object temporarySessionCacheLock = new object(); + private static readonly object temporarySessionCacheLock = new(); /// /// temporary CimSession cache @@ -218,7 +210,7 @@ internal class CimSessionProxy : IDisposable /// then call Dispose on it. /// /// - private static Dictionary temporarySessionCache = new Dictionary(); + private static readonly Dictionary temporarySessionCache = new(); /// /// @@ -304,88 +296,80 @@ internal static void RemoveCimSessionFromTemporaryCache(CimSession session) #region Event definitions - /// - /// Define delegate that handles new cmdlet action come from - /// the operations related to the current CimSession object. - /// - /// CimSession object, which raised the event. - /// Event args. - public delegate void NewCmdletActionHandler( - object cimSession, - CmdletActionEventArgs actionArgs); - /// /// Define an Event based on the NewActionHandler. /// - public event NewCmdletActionHandler OnNewCmdletAction; - - /// - /// Define delegate that handles operation creation and complete - /// issued by the current CimSession object. - /// - /// CimSession object, which raised the event. - /// Event args. - public delegate void OperationEventHandler( - object cimSession, - OperationEventArgs actionArgs); + public event EventHandler OnNewCmdletAction; /// /// Event triggered when a new operation is started. /// - public event OperationEventHandler OnOperationCreated; + public event EventHandler OnOperationCreated; /// /// Event triggered when a new operation is completed, /// either success or failed. /// - public event OperationEventHandler OnOperationDeleted; + public event EventHandler OnOperationDeleted; #endregion #region constructors /// - /// Then create wrapper object by given CimSessionProxy object. + /// Initializes a new instance of the class. /// + /// + /// Then create wrapper object by given CimSessionProxy object. + /// /// public CimSessionProxy(CimSessionProxy proxy) { DebugHelper.WriteLogEx("protocol = {0}", 1, proxy.Protocol); CreateSetSession(null, proxy.CimSession, null, proxy.OperationOptions, proxy.IsTemporaryCimSession); - this.protocol = proxy.Protocol; + this.Protocol = proxy.Protocol; this.OperationTimeout = proxy.OperationTimeout; this.isDefaultSession = proxy.isDefaultSession; } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name. /// Then create wrapper object. - /// + /// /// public CimSessionProxy(string computerName) { CreateSetSession(computerName, null, null, null, false); - this.isDefaultSession = (computerName == ConstValue.NullComputerName); + this.isDefaultSession = computerName == ConstValue.NullComputerName; } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name /// and session options. /// Then create wrapper object. - /// + /// /// /// public CimSessionProxy(string computerName, CimSessionOptions sessionOptions) { CreateSetSession(computerName, null, sessionOptions, null, false); - this.isDefaultSession = (computerName == ConstValue.NullComputerName); + this.isDefaultSession = computerName == ConstValue.NullComputerName; } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name /// and cimInstance. Then create wrapper object. - /// + /// /// /// public CimSessionProxy(string computerName, CimInstance cimInstance) @@ -419,39 +403,48 @@ public CimSessionProxy(string computerName, CimInstance cimInstance) string cimsessionComputerName = cimInstance.GetCimSessionComputerName(); CreateSetSession(cimsessionComputerName, null, null, null, false); - this.isDefaultSession = (cimsessionComputerName == ConstValue.NullComputerName); + this.isDefaultSession = cimsessionComputerName == ConstValue.NullComputerName; DebugHelper.WriteLogEx("Create a temp session with computerName = {0}.", 0, cimsessionComputerName); } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name, /// session options. - /// + /// /// /// /// Used when create async operation. public CimSessionProxy(string computerName, CimSessionOptions sessionOptions, CimOperationOptions operOptions) { CreateSetSession(computerName, null, sessionOptions, operOptions, false); - this.isDefaultSession = (computerName == ConstValue.NullComputerName); + this.isDefaultSession = computerName == ConstValue.NullComputerName; } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name. /// Then create wrapper object. - /// + /// /// /// Used when create async operation. public CimSessionProxy(string computerName, CimOperationOptions operOptions) { CreateSetSession(computerName, null, null, operOptions, false); - this.isDefaultSession = (computerName == ConstValue.NullComputerName); + this.isDefaultSession = computerName == ConstValue.NullComputerName; } /// - /// Create wrapper object by given session object. + /// Initializes a new instance of the class. /// + /// + /// Create wrapper object by given session object. + /// /// public CimSessionProxy(CimSession session) { @@ -459,8 +452,11 @@ public CimSessionProxy(CimSession session) } /// - /// Create wrapper object by given session object. + /// Initializes a new instance of the class. /// + /// + /// Create wrapper object by given session object. + /// /// /// Used when create async operation. public CimSessionProxy(CimSession session, CimOperationOptions operOptions) @@ -491,19 +487,19 @@ private void CreateSetSession( } InitOption(operOptions); - this.protocol = ProtocolType.Wsman; - this.isTemporaryCimSession = temporaryCimSession; + this.Protocol = ProtocolType.Wsman; + this.IsTemporaryCimSession = temporaryCimSession; if (cimSession != null) { - this.session = cimSession; + this.CimSession = cimSession; CimSessionState state = CimSessionBase.GetCimSessionState(); if (state != null) { CimSessionWrapper wrapper = state.QuerySession(cimSession); if (wrapper != null) { - this.protocol = wrapper.GetProtocolType(); + this.Protocol = wrapper.GetProtocolType(); } } } @@ -514,29 +510,29 @@ private void CreateSetSession( if (sessionOptions is DComSessionOptions) { string defaultComputerName = ConstValue.IsDefaultComputerName(computerName) ? ConstValue.NullComputerName : computerName; - this.session = CimSession.Create(defaultComputerName, sessionOptions); - this.protocol = ProtocolType.Dcom; + this.CimSession = CimSession.Create(defaultComputerName, sessionOptions); + this.Protocol = ProtocolType.Dcom; } else { - this.session = CimSession.Create(computerName, sessionOptions); + this.CimSession = CimSession.Create(computerName, sessionOptions); } } else { - this.session = CreateCimSessionByComputerName(computerName); + this.CimSession = CreateCimSessionByComputerName(computerName); } - this.isTemporaryCimSession = true; + this.IsTemporaryCimSession = true; } - if (this.isTemporaryCimSession) + if (this.IsTemporaryCimSession) { - AddCimSessionToTemporaryCache(this.session); + AddCimSessionToTemporaryCache(this.CimSession); } this.invocationContextObject = new InvocationContext(this); - DebugHelper.WriteLog("Protocol {0}, Is temporary session ? {1}", 1, this.protocol, this.isTemporaryCimSession); + DebugHelper.WriteLog("Protocol {0}, Is temporary session ? {1}", 1, this.Protocol, this.IsTemporaryCimSession); } #endregion @@ -544,20 +540,40 @@ private void CreateSetSession( #region set operation options /// - /// Set timeout value (seconds) of the operation. + /// Gets or sets a value indicating whether to retrieve localized information for the CIM class. /// - public UInt32 OperationTimeout + public bool Amended { + get => OperationOptions.Flags.HasFlag(CimOperationFlags.LocalizedQualifiers); + set { - DebugHelper.WriteLogEx("OperationTimeout {0},", 0, value); - - this.options.Timeout = TimeSpan.FromSeconds((double)value); + if (value) + { + OperationOptions.Flags |= CimOperationFlags.LocalizedQualifiers; + } + else + { + OperationOptions.Flags &= ~CimOperationFlags.LocalizedQualifiers; + } } + } + /// + /// Set timeout value (seconds) of the operation. + /// + public uint OperationTimeout + { get { - return (UInt32)this.options.Timeout.TotalSeconds; + return (uint)this.OperationOptions.Timeout.TotalSeconds; + } + + set + { + DebugHelper.WriteLogEx("OperationTimeout {0},", 0, value); + + this.OperationOptions.Timeout = TimeSpan.FromSeconds((double)value); } } @@ -566,16 +582,16 @@ public UInt32 OperationTimeout /// public Uri ResourceUri { - set + get { - DebugHelper.WriteLogEx("ResourceUri {0},", 0, value); - - this.options.ResourceUri = value; + return this.OperationOptions.ResourceUri; } - get + set { - return this.options.ResourceUri; + DebugHelper.WriteLogEx("ResourceUri {0},", 0, value); + + this.OperationOptions.ResourceUri = value; } } @@ -587,13 +603,13 @@ public bool EnableMethodResultStreaming { get { - return this.options.EnableMethodResultStreaming; + return this.OperationOptions.EnableMethodResultStreaming; } set { DebugHelper.WriteLogEx("EnableMethodResultStreaming {0}", 0, value); - this.options.EnableMethodResultStreaming = value; + this.OperationOptions.EnableMethodResultStreaming = value; } } @@ -608,7 +624,7 @@ public bool EnablePromptUser DebugHelper.WriteLogEx("EnablePromptUser {0}", 0, value); if (value) { - this.options.PromptUser = this.PromptUser; + this.OperationOptions.PromptUser = this.PromptUser; } } } @@ -622,15 +638,15 @@ private void EnablePSSemantics() // this.options.PromptUserForceFlag... // this.options.WriteErrorMode - this.options.WriteErrorMode = CimCallbackMode.Inquire; + this.OperationOptions.WriteErrorMode = CimCallbackMode.Inquire; // !!!NOTES: Does not subscribe to PromptUser for CimCmdlets now // since cmdlet does not provider an approach // to let user select how to handle prompt message // this can be enabled later if needed. - this.options.WriteError = this.WriteError; - this.options.WriteMessage = this.WriteMessage; - this.options.WriteProgress = this.WriteProgress; + this.OperationOptions.WriteError = this.WriteError; + this.OperationOptions.WriteMessage = this.WriteMessage; + this.OperationOptions.WriteProgress = this.WriteProgress; } /// @@ -638,7 +654,7 @@ private void EnablePSSemantics() /// public SwitchParameter KeyOnly { - set { this.options.KeysOnly = value.IsPresent; } + set { this.OperationOptions.KeysOnly = value.IsPresent; } } /// @@ -650,11 +666,11 @@ public SwitchParameter Shallow { if (value.IsPresent) { - this.options.Flags = CimOperationFlags.PolymorphismShallow; + this.OperationOptions.Flags = CimOperationFlags.PolymorphismShallow; } else { - this.options.Flags = CimOperationFlags.None; + this.OperationOptions.Flags = CimOperationFlags.None; } } } @@ -668,11 +684,11 @@ private void InitOption(CimOperationOptions operOptions) if (operOptions != null) { - this.options = new CimOperationOptions(operOptions); + this.OperationOptions = new CimOperationOptions(operOptions); } - else if (this.options == null) + else { - this.options = new CimOperationOptions(); + this.OperationOptions ??= new CimOperationOptions(); } this.EnableMethodResultStreaming = true; @@ -693,10 +709,10 @@ public CimSession Detach() DebugHelper.WriteLogEx(); // Remove the CimSession from cache but don't dispose it - RemoveCimSessionFromTemporaryCache(this.session, false); - CimSession sessionToReturn = this.session; - this.session = null; - this.isTemporaryCimSession = false; + RemoveCimSessionFromTemporaryCache(this.CimSession, false); + CimSession sessionToReturn = this.CimSession; + this.CimSession = null; + this.IsTemporaryCimSession = false; return sessionToReturn; } @@ -735,7 +751,7 @@ private void RemoveOperation(IObservable operation) this.operation = null; } - if (this.session != null && this.ContextObject == null) + if (this.CimSession != null && this.ContextObject == null) { DebugHelper.WriteLog("Dispose this proxy object @ RemoveOperation"); this.Dispose(); @@ -753,16 +769,16 @@ protected void FireNewActionEvent(CimBaseAction action) { DebugHelper.WriteLogEx(); - CmdletActionEventArgs actionArgs = new CmdletActionEventArgs(action); + CmdletActionEventArgs actionArgs = new(action); if (!PreNewActionEvent(actionArgs)) { return; } - NewCmdletActionHandler temp = this.OnNewCmdletAction; + EventHandler temp = this.OnNewCmdletAction; if (temp != null) { - temp(this.session, actionArgs); + temp(this.CimSession, actionArgs); } else { @@ -785,13 +801,9 @@ private void FireOperationCreatedEvent( { DebugHelper.WriteLogEx(); - OperationEventArgs args = new OperationEventArgs( + OperationEventArgs args = new( cancelOperation, operation, false); - OperationEventHandler temp = this.OnOperationCreated; - if (temp != null) - { - temp(this.session, args); - } + this.OnOperationCreated?.Invoke(this.CimSession, args); this.PostOperationCreateEvent(args); } @@ -808,14 +820,10 @@ private void FireOperationDeletedEvent( { DebugHelper.WriteLogEx(); this.WriteOperationCompleteMessage(this.operationName); - OperationEventArgs args = new OperationEventArgs( + OperationEventArgs args = new( null, operation, success); PreOperationDeleteEvent(args); - OperationEventHandler temp = this.OnOperationDeleted; - if (temp != null) - { - temp(this.session, args); - } + this.OnOperationDeleted?.Invoke(this.CimSession, args); this.PostOperationDeleteEvent(args); this.RemoveOperation(operation); @@ -833,12 +841,12 @@ 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 { - CimWriteMessage action = new CimWriteMessage(channel, message); + CimWriteMessage action = new(channel, message); this.FireNewActionEvent(action); } catch (Exception ex) @@ -857,17 +865,17 @@ internal void WriteMessage(UInt32 channel, string message) internal void WriteOperationStartMessage(string operation, Hashtable parameterList) { DebugHelper.WriteLogEx(); - StringBuilder parameters = new StringBuilder(); + StringBuilder parameters = new(); if (parameterList != null) { foreach (string key in parameterList.Keys) { if (parameters.Length > 0) { - parameters.Append(","); + parameters.Append(','); } - parameters.Append(string.Format(CultureInfo.CurrentUICulture, @"'{0}' = {1}", key, parameterList[key])); + parameters.Append(CultureInfo.CurrentUICulture, $@"'{key}' = {parameterList[key]}"); } } @@ -875,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); } /// @@ -890,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); } /// @@ -906,15 +914,15 @@ 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); try { - CimWriteProgress action = new CimWriteProgress( + CimWriteProgress action = new( activity, (int)this.operationID, currentOperation, @@ -941,7 +949,7 @@ public CimResponseType WriteError(CimInstance instance) DebugHelper.WriteLogEx("Error:{0}", 0, instance); try { - CimWriteError action = new CimWriteError(instance, this.invocationContextObject); + CimWriteError action = new(instance, this.invocationContextObject); this.FireNewActionEvent(action); return action.GetResponse(); } @@ -963,7 +971,7 @@ public CimResponseType PromptUser(string message, CimPromptType prompt) DebugHelper.WriteLogEx("message:{0} prompt:{1}", 0, message, prompt); try { - CimPromptUser action = new CimPromptUser(message, prompt); + CimPromptUser action = new(message, prompt); this.FireNewActionEvent(action); return action.GetResponse(); } @@ -979,7 +987,7 @@ public CimResponseType PromptUser(string message, CimPromptType prompt) /// /// - /// Handle async event triggered by + /// Handle async event triggered by /// /// /// Object triggered the event. @@ -1005,7 +1013,7 @@ internal void ResultEventHandler( AsyncResultErrorEventArgs args = resultArgs as AsyncResultErrorEventArgs; DebugHelper.WriteLog("ResultEventHandler::Exception {0}", 4, args.error); - using (CimWriteError action = new CimWriteError(args.error, this.invocationContextObject, args.context)) + using (CimWriteError action = new(args.error, this.invocationContextObject, args.context)) { this.FireNewActionEvent(action); } @@ -1031,7 +1039,7 @@ internal void ResultEventHandler( #if DEBUG resultObject = PostProcessCimInstance(resultObject); #endif - CimWriteResultObject action = new CimWriteResultObject(resultObject, this.ContextObject); + CimWriteResultObject action = new(resultObject, this.ContextObject); this.FireNewActionEvent(action); } @@ -1055,24 +1063,24 @@ private static void AddShowComputerNameMarker(object o) } PSObject pso = PSObject.AsPSObject(o); - if (!(pso.BaseObject is CimInstance)) + if (pso.BaseObject is not CimInstance) { return; } - PSNoteProperty psShowComputerNameProperty = new PSNoteProperty(ConstValue.ShowComputerNameNoteProperty, true); + PSNoteProperty psShowComputerNameProperty = new(ConstValue.ShowComputerNameNoteProperty, true); pso.Members.Add(psShowComputerNameProperty); } #if DEBUG - private static bool isCliXmlTestabilityHookActive = GetIsCliXmlTestabilityHookActive(); + private static readonly bool isCliXmlTestabilityHookActive = GetIsCliXmlTestabilityHookActive(); private static bool GetIsCliXmlTestabilityHookActive() { return !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CDXML_CLIXML_TEST")); } - private object PostProcessCimInstance(object resultObject) + private static object PostProcessCimInstance(object resultObject) { DebugHelper.WriteLogEx(); if (isCliXmlTestabilityHookActive && (resultObject is CimInstance)) @@ -1101,15 +1109,15 @@ private object PostProcessCimInstance(object resultObject) public void CreateInstanceAsync(string namespaceName, CimInstance instance) { Debug.Assert(instance != null, "Caller should verify that instance != NULL."); - DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.options.EnableMethodResultStreaming); + DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.OperationOptions.EnableMethodResultStreaming); this.CheckAvailability(); - this.targetCimInstance = instance; + this.TargetCimInstance = instance; this.operationName = CimCmdletStrings.CimOperationNameCreateInstance; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"instance", instance); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncResult asyncResult = this.session.CreateInstanceAsync(namespaceName, instance, this.options); + CimAsyncResult asyncResult = this.CimSession.CreateInstanceAsync(namespaceName, instance, this.OperationOptions); ConsumeCimInstanceAsync(asyncResult, new CimResultContext(instance)); } @@ -1123,13 +1131,13 @@ public void DeleteInstanceAsync(string namespaceName, CimInstance instance) Debug.Assert(instance != null, "Caller should verify that instance != NULL."); DebugHelper.WriteLogEx("namespace = {0}; classname = {1};", 0, namespaceName, instance.CimSystemProperties.ClassName); this.CheckAvailability(); - this.targetCimInstance = instance; + this.TargetCimInstance = instance; this.operationName = CimCmdletStrings.CimOperationNameDeleteInstance; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"instance", instance); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncStatus asyncResult = this.session.DeleteInstanceAsync(namespaceName, instance, this.options); + CimAsyncStatus asyncResult = this.CimSession.DeleteInstanceAsync(namespaceName, instance, this.OperationOptions); ConsumeObjectAsync(asyncResult, new CimResultContext(instance)); } @@ -1141,15 +1149,15 @@ public void DeleteInstanceAsync(string namespaceName, CimInstance instance) public void GetInstanceAsync(string namespaceName, CimInstance instance) { Debug.Assert(instance != null, "Caller should verify that instance != NULL."); - DebugHelper.WriteLogEx("namespace = {0}; classname = {1}; keyonly = {2}", 0, namespaceName, instance.CimSystemProperties.ClassName, this.options.KeysOnly); + DebugHelper.WriteLogEx("namespace = {0}; classname = {1}; keyonly = {2}", 0, namespaceName, instance.CimSystemProperties.ClassName, this.OperationOptions.KeysOnly); this.CheckAvailability(); - this.targetCimInstance = instance; + this.TargetCimInstance = instance; this.operationName = CimCmdletStrings.CimOperationNameGetInstance; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"instance", instance); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncResult asyncResult = this.session.GetInstanceAsync(namespaceName, instance, this.options); + CimAsyncResult asyncResult = this.CimSession.GetInstanceAsync(namespaceName, instance, this.OperationOptions); ConsumeCimInstanceAsync(asyncResult, new CimResultContext(instance)); } @@ -1163,13 +1171,13 @@ public void ModifyInstanceAsync(string namespaceName, CimInstance instance) Debug.Assert(instance != null, "Caller should verify that instance != NULL."); DebugHelper.WriteLogEx("namespace = {0}; classname = {1}", 0, namespaceName, instance.CimSystemProperties.ClassName); this.CheckAvailability(); - this.targetCimInstance = instance; + this.TargetCimInstance = instance; this.operationName = CimCmdletStrings.CimOperationNameModifyInstance; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"instance", instance); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncResult asyncResult = this.session.ModifyInstanceAsync(namespaceName, instance, this.options); + CimAsyncResult asyncResult = this.CimSession.ModifyInstanceAsync(namespaceName, instance, this.OperationOptions); ConsumeObjectAsync(asyncResult, new CimResultContext(instance)); } @@ -1194,7 +1202,7 @@ public void EnumerateAssociatedInstancesAsync( Debug.Assert(sourceInstance != null, "Caller should verify that sourceInstance != NULL."); DebugHelper.WriteLogEx("Instance class {0}, association class {1}", 0, sourceInstance.CimSystemProperties.ClassName, associationClassName); this.CheckAvailability(); - this.targetCimInstance = sourceInstance; + this.TargetCimInstance = sourceInstance; this.operationName = CimCmdletStrings.CimOperationNameEnumerateAssociatedInstances; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); @@ -1204,7 +1212,7 @@ public void EnumerateAssociatedInstancesAsync( this.operationParameters.Add(@"sourceRole", sourceRole); this.operationParameters.Add(@"resultRole", resultRole); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.EnumerateAssociatedInstancesAsync(namespaceName, sourceInstance, associationClassName, resultClassName, sourceRole, resultRole, this.options); + CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateAssociatedInstancesAsync(namespaceName, sourceInstance, associationClassName, resultClassName, sourceRole, resultRole, this.OperationOptions); ConsumeCimInstanceAsync(asyncResult, new CimResultContext(sourceInstance)); } @@ -1215,16 +1223,16 @@ public void EnumerateAssociatedInstancesAsync( /// public void EnumerateInstancesAsync(string namespaceName, string className) { - DebugHelper.WriteLogEx("KeyOnly {0}", 0, this.options.KeysOnly); + DebugHelper.WriteLogEx("KeyOnly {0}", 0, this.OperationOptions.KeysOnly); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameEnumerateInstances; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.EnumerateInstancesAsync(namespaceName, className, this.options); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateInstancesAsync(namespaceName, className, this.OperationOptions); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimInstanceAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1260,16 +1268,16 @@ public void QueryInstancesAsync( string queryDialect, string queryExpression) { - DebugHelper.WriteLogEx("KeyOnly = {0}", 0, this.options.KeysOnly); + DebugHelper.WriteLogEx("KeyOnly = {0}", 0, this.OperationOptions.KeysOnly); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameQueryInstances; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"queryDialect", queryDialect); this.operationParameters.Add(@"queryExpression", queryExpression); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.QueryInstancesAsync(namespaceName, queryDialect, queryExpression, this.options); + CimAsyncMultipleResults asyncResult = this.CimSession.QueryInstancesAsync(namespaceName, queryDialect, queryExpression, this.OperationOptions); ConsumeCimInstanceAsync(asyncResult, null); } @@ -1282,12 +1290,12 @@ public void EnumerateClassesAsync(string namespaceName) { DebugHelper.WriteLogEx("namespace {0}", 0, namespaceName); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameEnumerateClasses; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.EnumerateClassesAsync(namespaceName, null, this.options); + CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateClassesAsync(namespaceName, null, this.OperationOptions); ConsumeCimClassAsync(asyncResult, null); } @@ -1299,14 +1307,14 @@ public void EnumerateClassesAsync(string namespaceName) public void EnumerateClassesAsync(string namespaceName, string className) { this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameEnumerateClasses; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.EnumerateClassesAsync(namespaceName, className, this.options); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + CimAsyncMultipleResults asyncResult = this.CimSession.EnumerateClassesAsync(namespaceName, className, this.OperationOptions); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimClassAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1319,14 +1327,14 @@ public void GetClassAsync(string namespaceName, string className) { DebugHelper.WriteLogEx("namespace = {0}, className = {1}", 0, namespaceName, className); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameGetClass; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"className", className); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncResult asyncResult = this.session.GetClassAsync(namespaceName, className, this.options); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + CimAsyncResult asyncResult = this.CimSession.GetClassAsync(namespaceName, className, this.OperationOptions); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimClassAsync(asyncResult, new CimResultContext(errorSource)); } @@ -1344,16 +1352,16 @@ public void InvokeMethodAsync( CimMethodParametersCollection methodParameters) { Debug.Assert(instance != null, "Caller should verify that instance != NULL."); - DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.options.EnableMethodResultStreaming); + DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.OperationOptions.EnableMethodResultStreaming); this.CheckAvailability(); - this.targetCimInstance = instance; + this.TargetCimInstance = instance; this.operationName = CimCmdletStrings.CimOperationNameInvokeMethod; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"instance", instance); this.operationParameters.Add(@"methodName", methodName); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.InvokeMethodAsync(namespaceName, instance, methodName, methodParameters, this.options); + CimAsyncMultipleResults asyncResult = this.CimSession.InvokeMethodAsync(namespaceName, instance, methodName, methodParameters, this.OperationOptions); ConsumeCimInvokeMethodResultAsync(asyncResult, instance.CimSystemProperties.ClassName, methodName, new CimResultContext(instance)); } @@ -1370,17 +1378,17 @@ public void InvokeMethodAsync( string methodName, CimMethodParametersCollection methodParameters) { - DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.options.EnableMethodResultStreaming); + DebugHelper.WriteLogEx("EnableMethodResultStreaming = {0}", 0, this.OperationOptions.EnableMethodResultStreaming); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameInvokeMethod; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); this.operationParameters.Add(@"className", className); this.operationParameters.Add(@"methodName", methodName); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - CimAsyncMultipleResults asyncResult = this.session.InvokeMethodAsync(namespaceName, className, methodName, methodParameters, this.options); - string errorSource = string.Format(CultureInfo.CurrentUICulture, "{0}:{1}", namespaceName, className); + CimAsyncMultipleResults asyncResult = this.CimSession.InvokeMethodAsync(namespaceName, className, methodName, methodParameters, this.OperationOptions); + string errorSource = string.Create(CultureInfo.CurrentUICulture, $"{namespaceName}:{className}"); ConsumeCimInvokeMethodResultAsync(asyncResult, className, methodName, new CimResultContext(errorSource)); } @@ -1399,7 +1407,7 @@ public void SubscribeAsync( { DebugHelper.WriteLogEx("QueryDialect = '{0}'; queryExpression = '{1}'", 0, queryDialect, queryExpression); this.CheckAvailability(); - this.targetCimInstance = null; + this.TargetCimInstance = null; this.operationName = CimCmdletStrings.CimOperationNameSubscribeIndication; this.operationParameters.Clear(); this.operationParameters.Add(@"namespaceName", namespaceName); @@ -1407,8 +1415,8 @@ public void SubscribeAsync( this.operationParameters.Add(@"queryExpression", queryExpression); this.WriteOperationStartMessage(this.operationName, this.operationParameters); - this.options.Flags |= CimOperationFlags.ReportOperationStarted; - CimAsyncMultipleResults asyncResult = this.session.SubscribeAsync(namespaceName, queryDialect, queryExpression, this.options); + this.OperationOptions.Flags |= CimOperationFlags.ReportOperationStarted; + CimAsyncMultipleResults asyncResult = this.CimSession.SubscribeAsync(namespaceName, queryDialect, queryExpression, this.OperationOptions); ConsumeCimSubscriptionResultAsync(asyncResult, null); } @@ -1421,8 +1429,8 @@ public void TestConnectionAsync() { DebugHelper.WriteLogEx("Start test connection", 0); this.CheckAvailability(); - this.targetCimInstance = null; - CimAsyncResult asyncResult = this.session.TestConnectionAsync(); + this.TargetCimInstance = null; + CimAsyncResult asyncResult = this.CimSession.TestConnectionAsync(); // ignore the test connection result objects ConsumeCimInstanceAsync(asyncResult, true, null); } @@ -1485,44 +1493,17 @@ 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 - { - return this.session; - } - } - - private CimSession session; + internal CimSession CimSession { get; private set; } /// /// The current CimInstance object, against which issued /// current operation, it could be null. /// - internal CimInstance TargetCimInstance - { - get - { - return this.targetCimInstance; - } - } - - private CimInstance targetCimInstance = null; - - /// - /// Flag controls whether session object should be closed or not. - /// - private bool isTemporaryCimSession; + internal CimInstance TargetCimInstance { get; private set; } - internal bool IsTemporaryCimSession - { - get - { - return isTemporaryCimSession; - } - } + internal bool IsTemporaryCimSession { get; private set; } /// /// The CimOperationOptions object, which specifies the options @@ -1532,15 +1513,7 @@ internal bool IsTemporaryCimSession /// The setting MUST be set before start new operation on the /// this proxy object. /// - internal CimOperationOptions OperationOptions - { - get - { - return this.options; - } - } - - private CimOperationOptions options; + internal CimOperationOptions OperationOptions { get; private set; } /// /// All operations completed. @@ -1554,7 +1527,7 @@ private bool Completed /// Lock object used to lock /// operation & cancelOperation members. /// - private readonly object stateLock = new object(); + private readonly object stateLock = new(); /// /// The operation issued by cimSession. @@ -1569,7 +1542,7 @@ private bool Completed /// /// The current operation parameters. /// - private Hashtable operationParameters = new Hashtable(); + private readonly Hashtable operationParameters = new(); /// /// Handler used to cancel operation. @@ -1603,50 +1576,29 @@ private void DisposeCancelOperation() /// private IDisposable CancelOperation { + get + { + return this._cancelOperation; + } + set { DebugHelper.WriteLogEx(); this._cancelOperation = value; Interlocked.Exchange(ref this._cancelOperationDisposed, 0); } - - get - { - return this._cancelOperation; - } } /// /// Current protocol name /// DCOM or WSMAN. /// - internal ProtocolType Protocol - { - get - { - return protocol; - } - } - - private ProtocolType protocol; + internal ProtocolType Protocol { get; private set; } /// /// Cross operation context object. /// - internal XOperationContextBase ContextObject - { - set - { - this.contextObject = value; - } - - get - { - return this.contextObject; - } - } - - private XOperationContextBase contextObject; + internal XOperationContextBase ContextObject { get; set; } /// /// Invocation context object. @@ -1657,27 +1609,14 @@ internal XOperationContextBase ContextObject /// A preprocess object to pre-processing the result object, /// for example, adding PSTypeName, etc. /// - internal IObjectPreProcess ObjectPreProcess - { - set - { - this.objectPreprocess = value; - } - - get - { - return this.objectPreprocess; - } - } - - private IObjectPreProcess objectPreprocess; + internal IObjectPreProcess ObjectPreProcess { get; set; } /// - /// is true if this was + /// is if this was /// created to handle the "default" session, in cases where cmdlets are invoked without /// ComputerName and/or CimSession parameters. /// - private bool isDefaultSession; + private readonly bool isDefaultSession; #endregion @@ -1720,10 +1659,10 @@ protected virtual void Dispose(bool disposing) // Dispose managed resources. this.DisposeCancelOperation(); - if (this.options != null) + if (this.OperationOptions != null) { - this.options.Dispose(); - this.options = null; + this.OperationOptions.Dispose(); + this.OperationOptions = null; } DisposeTemporaryCimSession(); @@ -1746,12 +1685,12 @@ public bool IsDisposed /// private void DisposeTemporaryCimSession() { - if (this.isTemporaryCimSession && this.session != null) + if (this.IsTemporaryCimSession && this.CimSession != null) { // remove the cimsession from temporary cache - RemoveCimSessionFromTemporaryCache(this.session); - this.isTemporaryCimSession = false; - this.session = null; + RemoveCimSessionFromTemporaryCache(this.CimSession); + this.IsTemporaryCimSession = false; + this.CimSession = null; } } #endregion @@ -1787,11 +1726,11 @@ protected void ConsumeCimInstanceAsync( CimResultObserver observer; if (ignoreResultObjects) { - observer = new IgnoreResultObserver(this.session, asyncResult); + observer = new IgnoreResultObserver(this.CimSession, asyncResult); } else { - observer = new CimResultObserver(this.session, asyncResult, cimResultContext); + observer = new CimResultObserver(this.CimSession, asyncResult, cimResultContext); } observer.OnNewResult += this.ResultEventHandler; @@ -1811,8 +1750,8 @@ protected void ConsumeCimInstanceAsync( protected void ConsumeObjectAsync(IObservable asyncResult, CimResultContext cimResultContext) { - CimResultObserver observer = new CimResultObserver( - this.session, asyncResult, cimResultContext); + CimResultObserver observer = new( + this.CimSession, asyncResult, cimResultContext); observer.OnNewResult += this.ResultEventHandler; this.operationID = Interlocked.Increment(ref gOperationCounter); @@ -1832,8 +1771,8 @@ protected void ConsumeObjectAsync(IObservable asyncResult, protected void ConsumeCimClassAsync(IObservable asyncResult, CimResultContext cimResultContext) { - CimResultObserver observer = new CimResultObserver( - this.session, asyncResult, cimResultContext); + CimResultObserver observer = new( + this.CimSession, asyncResult, cimResultContext); observer.OnNewResult += this.ResultEventHandler; this.operationID = Interlocked.Increment(ref gOperationCounter); @@ -1853,8 +1792,8 @@ protected void ConsumeCimSubscriptionResultAsync( IObservable asyncResult, CimResultContext cimResultContext) { - CimSubscriptionResultObserver observer = new CimSubscriptionResultObserver( - this.session, asyncResult, cimResultContext); + CimSubscriptionResultObserver observer = new( + this.CimSession, asyncResult, cimResultContext); observer.OnNewResult += this.ResultEventHandler; this.operationID = Interlocked.Increment(ref gOperationCounter); this.AddOperation(asyncResult); @@ -1877,7 +1816,7 @@ protected void ConsumeCimInvokeMethodResultAsync( string methodName, CimResultContext cimResultContext) { - CimMethodResultObserver observer = new CimMethodResultObserver(this.session, asyncResult, cimResultContext) + CimMethodResultObserver observer = new(this.CimSession, asyncResult, cimResultContext) { ClassName = className, MethodName = methodName @@ -1908,7 +1847,7 @@ private void CheckAvailability() } } - DebugHelper.WriteLog("KeyOnly {0},", 1, this.options.KeysOnly); + DebugHelper.WriteLog("KeyOnly {0},", 1, this.OperationOptions.KeysOnly); } /// @@ -1918,9 +1857,9 @@ private void CheckAvailability() /// private void AssertSession() { - if (this.IsDisposed || (this.session == null)) + if (this.IsDisposed || (this.CimSession == null)) { - DebugHelper.WriteLogEx("Invalid CimSessionProxy object, disposed? {0}; session object {1}", 1, this.IsDisposed, this.session); + DebugHelper.WriteLogEx("Invalid CimSessionProxy object, disposed? {0}; session object {1}", 1, this.IsDisposed, this.CimSession); throw new ObjectDisposedException(this.ToString()); } } @@ -1939,7 +1878,7 @@ private CimSession CreateCimSessionByComputerName(string computerName) if (option is DComSessionOptions) { DebugHelper.WriteLog("Create dcom cimSession"); - this.protocol = ProtocolType.Dcom; + this.Protocol = ProtocolType.Dcom; return CimSession.Create(ConstValue.NullComputerName, option); } else @@ -1960,7 +1899,7 @@ private CimSession CreateCimSessionByComputerName(string computerName) /// /// internal static CimSessionOptions CreateCimSessionOption(string computerName, - UInt32 timeout, CimCredential credential) + uint timeout, CimCredential credential) { DebugHelper.WriteLogEx(); @@ -2005,10 +1944,13 @@ internal class CimSessionProxyTestConnection : CimSessionProxy #region constructors /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name /// and session options. /// Then create wrapper object. - /// + /// /// /// public CimSessionProxyTestConnection(string computerName, CimSessionOptions sessionOptions) @@ -2031,7 +1973,7 @@ protected override void PreOperationDeleteEvent(OperationEventArgs args) if (args.success) { // test connection success, write session object to pipeline - CimWriteResultObject result = new CimWriteResultObject(this.CimSession, this.ContextObject); + CimWriteResultObject result = new(this.CimSession, this.ContextObject); this.FireNewActionEvent(result); } } @@ -2054,9 +1996,12 @@ internal class CimSessionProxyGetCimClass : CimSessionProxy #region constructors /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name. /// Then create wrapper object. - /// + /// /// public CimSessionProxyGetCimClass(string computerName) : base(computerName) @@ -2064,10 +2009,13 @@ public CimSessionProxyGetCimClass(string computerName) } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name /// and session options. /// Then create wrapper object. - /// + /// /// /// public CimSessionProxyGetCimClass(CimSession session) @@ -2086,15 +2034,14 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) { DebugHelper.WriteLogEx(); - if (!(args.Action is CimWriteResultObject)) + if (args.Action is not CimWriteResultObject) { // allow all other actions return true; } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - CimClass cimClass = writeResultObject.Result as CimClass; - if (cimClass == null) + if (writeResultObject.Result is not CimClass cimClass) { return true; } @@ -2206,27 +2153,33 @@ internal class CimSessionProxyNewCimInstance : CimSessionProxy #region constructors /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name. /// Then create wrapper object. - /// - /// + /// public CimSessionProxyNewCimInstance(string computerName, CimNewCimInstance operation) : base(computerName) { - this.newCimInstance = operation; + this.NewCimInstanceOperation = operation; } /// + /// Initializes a new instance of the class. + /// + /// + /// /// Create by given computer name /// and session options. /// Then create wrapper object. - /// + /// /// /// public CimSessionProxyNewCimInstance(CimSession session, CimNewCimInstance operation) : base(session) { - this.newCimInstance = operation; + this.NewCimInstanceOperation = operation; } #endregion @@ -2240,15 +2193,14 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) { DebugHelper.WriteLogEx(); - if (!(args.Action is CimWriteResultObject)) + if (args.Action is not CimWriteResultObject) { // allow all other actions return true; } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - CimInstance cimInstance = writeResultObject.Result as CimInstance; - if (cimInstance == null) + if (writeResultObject.Result is not CimInstance cimInstance) { return true; } @@ -2261,15 +2213,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) #region private members - private CimNewCimInstance newCimInstance = null; - - internal CimNewCimInstance NewCimInstanceOperation - { - get - { - return this.newCimInstance; - } - } + internal CimNewCimInstance NewCimInstanceOperation { get; } #endregion } @@ -2287,9 +2231,12 @@ internal class CimSessionProxySetCimInstance : CimSessionProxy { #region constructors /// + /// Initializes a new instance of the class. + /// + /// /// Create by given object. /// Then create wrapper object. - /// + /// /// object to clone. /// PassThru, true means output the modified instance; otherwise does not output. public CimSessionProxySetCimInstance(CimSessionProxy originalProxy, bool passThru) @@ -2299,9 +2246,12 @@ public CimSessionProxySetCimInstance(CimSessionProxy originalProxy, bool passThr } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name. /// Then create wrapper object. - /// + /// /// /// /// @@ -2314,10 +2264,13 @@ public CimSessionProxySetCimInstance(string computerName, } /// + /// Initializes a new instance of the class. + /// + /// /// Create by given computer name /// and session options. /// Then create wrapper object. - /// + /// /// /// public CimSessionProxySetCimInstance(CimSession session, bool passThru) @@ -2351,7 +2304,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) /// /// Ture indicates need to output the modified result. /// - private bool passThru = false; + private readonly bool passThru = false; #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs index fbe0830318d..91cf332bde9 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSetCimInstance.cs @@ -3,11 +3,10 @@ #region Using directives -using System.Collections; using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Management.Automation; #endregion @@ -20,9 +19,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal class CimSetCimInstanceContext : XOperationContextBase { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// /// /// @@ -34,50 +31,26 @@ internal CimSetCimInstanceContext(string theNamespace, bool passThru) { this.proxy = theProxy; - this.property = theProperty; + this.Property = theProperty; this.nameSpace = theNamespace; - this.parameterSetName = theParameterSetName; - this.passThru = passThru; + this.ParameterSetName = theParameterSetName; + this.PassThru = passThru; } /// /// property value /// - internal IDictionary Property - { - get - { - return this.property; - } - } - - private IDictionary property; + internal IDictionary Property { get; } /// /// parameter set name /// - internal string ParameterSetName - { - get - { - return this.parameterSetName; - } - } - - private string parameterSetName; + internal string ParameterSetName { get; } /// /// PassThru value /// - internal bool PassThru - { - get - { - return this.passThru; - } - } - - private bool passThru; + internal bool PassThru { get; } } /// @@ -88,9 +61,7 @@ internal bool PassThru internal sealed class CimSetCimInstance : CimGetInstance { /// - /// - /// Constructor - /// + /// Initializes a new instance of the class. /// public CimSetCimInstance() : base() @@ -107,7 +78,7 @@ public void SetCimInstance(SetCimInstanceCommand cmdlet) { IEnumerable computerNames = ConstValue.GetComputerNames( GetComputerName(cmdlet)); - List proxys = new List(); + List proxys = new(); switch (cmdlet.ParameterSetName) { case CimBaseCommand.CimInstanceComputerSet: diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteError.cs index fbd780450ab..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, @@ -166,7 +164,7 @@ internal static ErrorRecord InitializeErrorRecordCore( } } - ErrorRecord coreErrorRecord = new ErrorRecord( + ErrorRecord coreErrorRecord = new( exception: exception, errorId: errorId, errorCategory: errorCategory, @@ -177,7 +175,7 @@ internal static ErrorRecord InitializeErrorRecordCore( return coreErrorRecord; } - System.Management.Automation.Remoting.OriginInfo originInfo = new System.Management.Automation.Remoting.OriginInfo( + System.Management.Automation.Remoting.OriginInfo originInfo = new( context.ComputerName, Guid.Empty); @@ -317,24 +315,26 @@ internal static ErrorCategory ConvertCimErrorToErrorCategory(CimInstance cimErro internal sealed class CimWriteError : CimSyncAction { /// - /// Constructor with an error. + /// Initializes a new instance of the class + /// with the specified . /// /// public CimWriteError(CimInstance error, InvocationContext context) { - this.error = error; - this.invocationContext = context; + this.Error = error; + this.CimInvocationContext = context; } /// - /// Construct with an exception object. + /// Initializes a new instance of the class + /// with the specified . /// /// public CimWriteError(Exception exception, InvocationContext context, CimResultContext cimResultContext) { - this.exception = exception; - this.invocationContext = context; - this.cimResultContext = cimResultContext; + this.Exception = exception; + this.CimInvocationContext = context; + this.ResultContext = cimResultContext; } /// @@ -348,10 +348,10 @@ public override void Execute(CmdletOperationBase cmdlet) Debug.Assert(cmdlet != null, "Caller should verify that cmdlet != null"); try { - Exception errorException = (error != null) ? new CimException(error) : this.Exception; + Exception errorException = (Error != null) ? new CimException(Error) : this.Exception; // PS engine takes care of handling error action - cmdlet.WriteError(ErrorToErrorRecord.ErrorRecordFromAnyException(this.invocationContext, errorException, this.cimResultContext)); + cmdlet.WriteError(ErrorToErrorRecord.ErrorRecordFromAnyException(this.CimInvocationContext, errorException, this.ResultContext)); // if user wants to continue, we will get here this.responseType = CimResponseType.Yes; @@ -375,59 +375,19 @@ public override void Execute(CmdletOperationBase cmdlet) /// Error instance /// /// - private CimInstance error; - internal CimInstance Error - { - get - { - return error; - } - } + internal CimInstance Error { get; } /// /// /// Exception object /// /// - internal Exception Exception - { - get - { - return exception; - } - } - - private Exception exception; + internal Exception Exception { get; } - /// - /// - /// object that contains - /// the information while issuing the current operation - /// - /// - private InvocationContext invocationContext; + internal InvocationContext CimInvocationContext { get; } - internal InvocationContext CimInvocationContext - { - get - { - return invocationContext; - } - } - - /// - /// - /// - private CimResultContext cimResultContext; - - internal CimResultContext ResultContext - { - get - { - return cimResultContext; - } - } + internal CimResultContext ResultContext { get; } #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs index b351813c4d4..f4b244b97f9 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteMessage.cs @@ -22,36 +22,24 @@ internal sealed class CimWriteMessage : CimBaseAction /// /// Channel id. /// - private UInt32 channel; - - /// - /// Message to write to the channel. - /// - private string message; #endregion #region Properties - internal UInt32 Channel - { - get { return channel; } - } + internal uint Channel { get; } - internal string Message - { - get { return message; } - } + internal string Message { get; } #endregion /// - /// Constructor method. + /// Initializes a new instance of the class. /// - public CimWriteMessage(UInt32 channel, + public CimWriteMessage(uint channel, string message) { - this.channel = channel; - this.message = message; + this.Channel = channel; + this.Message = message; } /// @@ -64,16 +52,16 @@ public override void Execute(CmdletOperationBase cmdlet) { ValidationHelper.ValidateNoNullArgument(cmdlet, "cmdlet"); - switch ((CimWriteMessageChannel)channel) + switch ((CimWriteMessageChannel)Channel) { case CimWriteMessageChannel.Verbose: - cmdlet.WriteVerbose(message); + cmdlet.WriteVerbose(Message); break; case CimWriteMessageChannel.Warning: - cmdlet.WriteWarning(message); + cmdlet.WriteWarning(Message); break; case CimWriteMessageChannel.Debug: - cmdlet.WriteDebug(message); + cmdlet.WriteDebug(Message); break; default: break; diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs index bd02c5dde42..b99407ccc81 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteProgress.cs @@ -18,7 +18,7 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal sealed class CimWriteProgress : CimBaseAction { /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// Activity identifier of the given activity @@ -40,23 +40,23 @@ public CimWriteProgress( int theActivityID, string theCurrentOperation, string theStatusDescription, - UInt32 thePercentageCompleted, - UInt32 theSecondsRemaining) + uint thePercentageCompleted, + uint theSecondsRemaining) { - this.activity = theActivity; - this.activityID = theActivityID; - this.currentOperation = theCurrentOperation; + this.Activity = theActivity; + this.ActivityID = theActivityID; + this.CurrentOperation = theCurrentOperation; if (string.IsNullOrEmpty(theStatusDescription)) { - this.statusDescription = CimCmdletStrings.DefaultStatusDescription; + this.StatusDescription = CimCmdletStrings.DefaultStatusDescription; } else { - this.statusDescription = theStatusDescription; + this.StatusDescription = theStatusDescription; } - this.percentageCompleted = thePercentageCompleted; - this.secondsRemaining = theSecondsRemaining; + this.PercentageCompleted = thePercentageCompleted; + this.SecondsRemaining = theSecondsRemaining; } /// @@ -70,84 +70,54 @@ public override void Execute(CmdletOperationBase cmdlet) DebugHelper.WriteLog( "...Activity {0}: id={1}, remain seconds ={2}, percentage completed = {3}", 4, - this.activity, - this.activityID, - this.secondsRemaining, - this.percentageCompleted); + this.Activity, + this.ActivityID, + this.SecondsRemaining, + this.PercentageCompleted); ValidationHelper.ValidateNoNullArgument(cmdlet, "cmdlet"); - ProgressRecord record = new ProgressRecord( - this.activityID, - this.activity, - this.statusDescription); - record.Activity = this.activity; + ProgressRecord record = new( + this.ActivityID, + this.Activity, + this.StatusDescription); + record.Activity = this.Activity; record.ParentActivityId = 0; - record.SecondsRemaining = (int)this.secondsRemaining; - record.PercentComplete = (int)this.percentageCompleted; + record.SecondsRemaining = (int)this.SecondsRemaining; + record.PercentComplete = (int)this.PercentageCompleted; cmdlet.WriteProgress(record); } #region members /// - /// Activity of the given activity. + /// Gets the activity of the given activity. /// - private string activity; + internal string Activity { get; } /// - /// Activity identifier of the given activity. + /// Gets the activity identifier of the given activity. /// - private int activityID; + internal int ActivityID { get; } /// - /// Current operation text of the given activity. + /// Gets the current operation text of the given activity. /// - private string currentOperation; + internal string CurrentOperation { get; } /// - /// Status description of the given activity. + /// Gets the status description of the given activity. /// - private string statusDescription; + internal string StatusDescription { get; } /// - /// Percentage completed of the given activity. + /// Gets the percentage completed of the given activity. /// - private UInt32 percentageCompleted; + internal uint PercentageCompleted { get; } /// - /// How many seconds remained for the given activity. + /// Gets the number of seconds remaining for the given activity. /// - private UInt32 secondsRemaining; - - internal string Activity - { - get { return activity; } - } - - internal int ActivityID - { - get { return activityID; } - } - - internal string CurrentOperation - { - get { return currentOperation; } - } - - internal string StatusDescription - { - get { return statusDescription; } - } - - internal UInt32 PercentageCompleted - { - get { return percentageCompleted; } - } - - internal UInt32 SecondsRemaining - { - get { return secondsRemaining; } - } + internal uint SecondsRemaining { get; } #endregion } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs index 91f3d574a12..a4dcdfaa5c4 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimWriteResultObject.cs @@ -15,11 +15,11 @@ namespace Microsoft.Management.Infrastructure.CimCmdlets internal sealed class CimWriteResultObject : CimBaseAction { /// - /// Constructor. + /// Initializes a new instance of the class. /// public CimWriteResultObject(object result, XOperationContextBase theContext) { - this.result = result; + this.Result = result; this.Context = theContext; } @@ -32,22 +32,14 @@ public CimWriteResultObject(object result, XOperationContextBase theContext) public override void Execute(CmdletOperationBase cmdlet) { ValidationHelper.ValidateNoNullArgument(cmdlet, "cmdlet"); - cmdlet.WriteObject(result, this.Context); + cmdlet.WriteObject(Result, this.Context); } #region members /// /// Result object. /// - internal object Result - { - get - { - return result; - } - } - - private object result; + internal object Result { get; } #endregion } } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs index bee7905cc52..bd1a2751622 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CmdletOperation.cs @@ -2,9 +2,8 @@ // Licensed under the MIT License. #region Using directives -using System.Management.Automation; using System; -using System.Globalization; +using System.Management.Automation; #endregion @@ -66,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); @@ -116,15 +116,16 @@ public virtual void WriteWarning(string text) /// Throw terminating error /// /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(Exception exception, string operation) { - ErrorRecord errorRecord = new ErrorRecord(exception, operation, ErrorCategory.InvalidOperation, this); + ErrorRecord errorRecord = new(exception, operation, ErrorCategory.InvalidOperation, this); cmdlet.ThrowTerminatingError(errorRecord); } #endregion /// - /// Constructor method. + /// Initializes a new instance of the class. /// public CmdletOperationBase(Cmdlet cmdlet) { @@ -144,7 +145,7 @@ public CmdletOperationBase(Cmdlet cmdlet) internal class CmdletOperationRemoveCimInstance : CmdletOperationBase { /// - /// Constructor method. + /// Initializes a new instance of the class. /// /// public CmdletOperationRemoveCimInstance(Cmdlet cmdlet, @@ -188,7 +189,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimRemoveCimInstance removeCimInstance; + private readonly CimRemoveCimInstance removeCimInstance; private const string cimRemoveCimInstanceParameterName = @"cimRemoveCimInstance"; @@ -208,7 +209,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection internal class CmdletOperationSetCimInstance : CmdletOperationBase { /// - /// Constructor method. + /// Initializes a new instance of the class. /// /// public CmdletOperationSetCimInstance(Cmdlet cmdlet, @@ -231,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)) @@ -268,7 +268,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimSetCimInstance setCimInstance; + private readonly CimSetCimInstance setCimInstance; private const string theCimSetCimInstanceParameterName = @"theCimSetCimInstance"; @@ -286,7 +286,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection internal class CmdletOperationInvokeCimMethod : CmdletOperationBase { /// - /// Constructor method. + /// Initializes a new instance of the class. /// /// public CmdletOperationInvokeCimMethod(Cmdlet cmdlet, @@ -331,7 +331,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection #region private methods - private CimInvokeCimMethod cimInvokeCimMethod; + private readonly CimInvokeCimMethod cimInvokeCimMethod; private const string theCimInvokeCimMethodParameterName = @"theCimInvokeCimMethod"; @@ -351,7 +351,7 @@ public override void WriteObject(object sendToPipeline, bool enumerateCollection internal class CmdletOperationTestCimSession : CmdletOperationBase { /// - /// Constructor method. + /// Initializes a new instance of the class. /// /// public CmdletOperationTestCimSession(Cmdlet cmdlet, @@ -392,7 +392,7 @@ public override void WriteObject(object sendToPipeline, XOperationContextBase co #region private methods - private CimNewSession cimNewSession; + private readonly CimNewSession cimNewSession; private const string theCimNewSessionParameterName = @"theCimNewSession"; diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs index 36987d35bd9..6c4d0c94d2b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimAssociatedInstanceCommand.cs @@ -4,9 +4,9 @@ #region Using directives using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using System.Collections.Generic; #endregion @@ -33,7 +33,7 @@ public class GetCimAssociatedInstanceCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public GetCimAssociatedInstanceCommand() : base(parameters, parameterSets) @@ -53,14 +53,7 @@ public GetCimAssociatedInstanceCommand() [Parameter( Position = 1, ValueFromPipelineByPropertyName = true)] - public string Association - { - get { return association; } - - set { association = value; } - } - - private string association; + public string Association { get; set; } /// /// The following is the definition of the input parameter "ResultClassName". @@ -68,14 +61,7 @@ public string Association /// the given instance. /// [Parameter] - public string ResultClassName - { - get { return resultClassName; } - - set { resultClassName = value; } - } - - private string resultClassName; + public string ResultClassName { get; set; } /// /// @@ -90,11 +76,14 @@ public string ResultClassName [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } @@ -102,12 +91,7 @@ public CimInstance InputObject /// /// Property for internal usage purpose. /// - internal CimInstance CimInstance - { - get { return cimInstance; } - } - - private CimInstance cimInstance; + internal CimInstance CimInstance { get; private set; } /// /// The following is the definition of the input parameter "Namespace". @@ -115,14 +99,7 @@ internal CimInstance CimInstance /// is registered. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string Namespace - { - get { return nameSpace; } - - set { nameSpace = value; } - } - - private string nameSpace; + public string Namespace { get; set; } /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -133,14 +110,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// @@ -151,7 +121,10 @@ public UInt32 OperationTimeoutSec [Parameter] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -179,7 +152,10 @@ public Uri ResourceUri [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } set { @@ -201,7 +177,10 @@ public string[] ComputerName [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Microsoft.Management.Infrastructure.CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -220,14 +199,7 @@ public Microsoft.Management.Infrastructure.CimSession[] CimSession /// /// [Parameter] - public SwitchParameter KeyOnly - { - get { return keyOnly; } - - set { keyOnly = value; } - } - - private SwitchParameter keyOnly; + public SwitchParameter KeyOnly { get; set; } #endregion @@ -260,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 @@ -315,7 +286,7 @@ private CimGetAssociatedInstance CreateOperationAgent() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameComputerName, new HashSet { @@ -344,7 +315,7 @@ private CimGetAssociatedInstance CreateOperationAgent() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.SessionSetName, new ParameterSetEntry(2, false) }, { CimBaseCommand.ComputerSetName, new ParameterSetEntry(1, true) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs index a439e263f0d..1bededc485f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimClassCommand.cs @@ -31,7 +31,7 @@ public class GetCimClassCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public GetCimClassCommand() : base(parameters, parameterSets) @@ -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". @@ -54,14 +60,7 @@ public GetCimClassCommand() [Parameter( Position = 0, ValueFromPipelineByPropertyName = true)] - public string ClassName - { - get { return className; } - - set { className = value; } - } - - private string className; + public string ClassName { get; set; } /// /// @@ -77,14 +76,7 @@ public string ClassName [Parameter( Position = 1, ValueFromPipelineByPropertyName = true)] - public string Namespace - { - get { return nameSpace; } - - set { nameSpace = value; } - } - - private string nameSpace; + public string Namespace { get; set; } /// /// The following is the definition of the input parameter "OperationTimeoutSec". @@ -93,14 +85,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "Session". @@ -113,7 +98,10 @@ public UInt32 OperationTimeoutSec [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -139,7 +127,10 @@ public CimSession[] CimSession [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } set { @@ -158,14 +149,7 @@ public string[] ComputerName /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string MethodName - { - get { return methodName; } - - set { methodName = value; } - } - - private string methodName; + public string MethodName { get; set; } /// /// @@ -175,14 +159,7 @@ public string MethodName /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string PropertyName - { - get { return propertyName; } - - set { propertyName = value; } - } - - private string propertyName; + public string PropertyName { get; set; } /// /// @@ -192,14 +169,7 @@ public string PropertyName /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string QualifierName - { - get { return qualifierName; } - - set { qualifierName = value; } - } - - private string qualifierName; + public string QualifierName { get; set; } #endregion @@ -232,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 @@ -250,7 +217,7 @@ protected override void EndProcessing() /// private CimGetCimClass GetOperationAgent() { - return (this.AsyncOperation as CimGetCimClass); + return this.AsyncOperation as CimGetCimClass; } /// @@ -262,7 +229,7 @@ private CimGetCimClass GetOperationAgent() /// private CimGetCimClass CreateOperationAgent() { - CimGetCimClass cimGetCimClass = new CimGetCimClass(); + CimGetCimClass cimGetCimClass = new(); this.AsyncOperation = cimGetCimClass; return cimGetCimClass; } @@ -288,7 +255,7 @@ private CimGetCimClass CreateOperationAgent() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -306,7 +273,7 @@ private CimGetCimClass CreateOperationAgent() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.SessionSetName, new ParameterSetEntry(1) }, { CimBaseCommand.ComputerSetName, new ParameterSetEntry(0, true) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs index 9e38546793b..65eeae8e450 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimInstanceCommand.cs @@ -5,9 +5,8 @@ using System; using System.Collections.Generic; -using System.Management.Automation; -using System.Collections; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; #endregion @@ -26,6 +25,7 @@ public class GetCimInstanceCommand : CimBaseCommand #region constructor /// + /// Initializes a new instance of the class. /// Constructor. /// public GetCimInstanceCommand() @@ -63,7 +63,10 @@ public GetCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -90,7 +93,10 @@ public CimSession[] CimSession ParameterSetName = CimBaseCommand.ClassNameComputerSet)] public string ClassName { - get { return className; } + get + { + return className; + } set { @@ -125,7 +131,10 @@ public string ClassName ParameterSetName = CimBaseCommand.QuerySessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -161,7 +170,10 @@ public Uri ResourceUri [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } set { @@ -185,7 +197,10 @@ public string[] ComputerName [Parameter(ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] public SwitchParameter KeyOnly { - get { return keyOnly; } + get + { + return keyOnly; + } set { @@ -220,7 +235,10 @@ public SwitchParameter KeyOnly ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } set { @@ -242,14 +260,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -277,11 +288,14 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } @@ -289,12 +303,7 @@ public CimInstance InputObject /// /// Property for internal usage purpose. /// - internal CimInstance CimInstance - { - get { return cimInstance; } - } - - private CimInstance cimInstance; + internal CimInstance CimInstance { get; private set; } /// /// The following is the definition of the input parameter "Query". @@ -309,7 +318,10 @@ internal CimInstance CimInstance ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Query { - get { return query; } + get + { + return query; + } set { @@ -338,7 +350,10 @@ public string Query public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } set { @@ -366,7 +381,10 @@ public string QueryDialect [Parameter(ParameterSetName = CimBaseCommand.QuerySessionSet)] public SwitchParameter Shallow { - get { return shallow; } + get + { + return shallow; + } set { @@ -393,7 +411,10 @@ public SwitchParameter Shallow ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] public string Filter { - get { return filter; } + get + { + return filter; + } set { @@ -422,23 +443,21 @@ public string Filter [Alias("SelectProperties")] public string[] Property { - get { return property; } + get + { + return SelectProperties; + } set { - property = value; + SelectProperties = value; base.SetParameter(value, nameSelectProperties); } } /// /// Property for internal usage. /// - internal string[] SelectProperties - { - get { return property; } - } - - private string[] property; + internal string[] SelectProperties { get; private set; } #endregion @@ -472,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 @@ -491,7 +507,7 @@ protected override void EndProcessing() /// private CimGetInstance GetOperationAgent() { - return (this.AsyncOperation as CimGetInstance); + return this.AsyncOperation as CimGetInstance; } /// @@ -504,7 +520,7 @@ private CimGetInstance GetOperationAgent() /// private CimGetInstance CreateOperationAgent() { - CimGetInstance cimGetInstance = new CimGetInstance(); + CimGetInstance cimGetInstance = new(); this.AsyncOperation = cimGetInstance; return cimGetInstance; } @@ -520,7 +536,7 @@ private void CheckArgument() case CimBaseCommand.ClassNameSessionSet: // validate the classname & property this.className = ValidationHelper.ValidateArgumentIsValidName(nameClassName, this.className); - this.property = ValidationHelper.ValidateArgumentIsValidName(nameSelectProperties, this.property); + this.SelectProperties = ValidationHelper.ValidateArgumentIsValidName(nameSelectProperties, this.SelectProperties); break; default: break; @@ -549,7 +565,7 @@ private void CheckArgument() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -652,7 +668,7 @@ private void CheckArgument() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimInstanceComputerSet, new ParameterSetEntry(1) }, { CimBaseCommand.CimInstanceSessionSet, new ParameterSetEntry(2) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs index 34d10b1d204..3289b6c86c0 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/GetCimSessionCommand.cs @@ -22,7 +22,7 @@ public sealed class GetCimSessionCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public GetCimSessionCommand() : base(parameters, parameterSets) @@ -58,7 +58,10 @@ public GetCimSessionCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -78,9 +81,12 @@ public string[] ComputerName ValueFromPipelineByPropertyName = true, ParameterSetName = SessionIdSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt32[] Id + public uint[] Id { - get { return id; } + get + { + return id; + } set { @@ -89,7 +95,7 @@ public UInt32[] Id } } - private UInt32[] id; + private uint[] id; /// /// The following is the definition of the input parameter "InstanceID". @@ -101,7 +107,10 @@ public UInt32[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Guid[] InstanceId { - get { return instanceid; } + get + { + return instanceid; + } set { @@ -123,7 +132,10 @@ public Guid[] InstanceId [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return name; } + get + { + return name; + } set { @@ -173,7 +185,7 @@ protected override void ProcessRecord() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameComputerName, new HashSet { @@ -200,7 +212,7 @@ protected override void ProcessRecord() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ComputerNameSet, new ParameterSetEntry(0, true) }, { CimBaseCommand.SessionIdSet, new ParameterSetEntry(1) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs index e777987a87b..e3bc6f293b6 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/InvokeCimMethodCommand.cs @@ -27,7 +27,7 @@ public class InvokeCimMethodCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public InvokeCimMethodCommand() : base(parameters, parameterSets) @@ -54,7 +54,10 @@ public InvokeCimMethodCommand() [Alias("Class")] public string ClassName { - get { return className; } + get + { + return className; + } set { @@ -83,7 +86,10 @@ public string ClassName ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -108,7 +114,10 @@ public Uri ResourceUri ParameterSetName = CimClassSessionSet)] public CimClass CimClass { - get { return cimClass; } + get + { + return cimClass; + } set { @@ -131,7 +140,10 @@ public CimClass CimClass ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Query { - get { return query; } + get + { + return query; + } set { @@ -155,7 +167,10 @@ public string Query ParameterSetName = CimBaseCommand.QuerySessionSet)] public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } set { @@ -182,11 +197,14 @@ public string QueryDialect [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } @@ -194,12 +212,7 @@ public CimInstance InputObject /// /// Property for internal usage purpose. /// - internal CimInstance CimInstance - { - get { return cimInstance; } - } - - private CimInstance cimInstance; + internal CimInstance CimInstance { get; private set; } /// /// The following is the definition of the input parameter "ComputerName". @@ -229,7 +242,10 @@ internal CimInstance CimInstance [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } set { @@ -269,7 +285,10 @@ public string[] ComputerName [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -287,14 +306,7 @@ public CimSession[] CimSession /// [Parameter(Position = 1, ValueFromPipelineByPropertyName = true)] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] - public IDictionary Arguments - { - get { return arguments; } - - set { arguments = value; } - } - - private IDictionary arguments; + public IDictionary Arguments { get; set; } /// /// The following is the definition of the input parameter "MethodName". @@ -306,7 +318,10 @@ public IDictionary Arguments [Alias("Name")] public string MethodName { - get { return methodName; } + get + { + return methodName; + } set { @@ -337,7 +352,10 @@ public string MethodName ParameterSetName = CimBaseCommand.ResourceUriSessionSet)] public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } set { @@ -355,14 +373,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } #endregion @@ -397,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 @@ -427,7 +435,7 @@ private CimInvokeCimMethod GetOperationAgent() /// private CimInvokeCimMethod CreateOperationAgent() { - CimInvokeCimMethod cimInvokeMethod = new CimInvokeCimMethod(); + CimInvokeCimMethod cimInvokeMethod = new(); this.AsyncOperation = cimInvokeMethod; return cimInvokeMethod; } @@ -469,7 +477,7 @@ private void CheckArgument() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -556,7 +564,7 @@ private void CheckArgument() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ClassNameComputerSet, new ParameterSetEntry(2, true) }, { CimBaseCommand.ResourceUriSessionSet, new ParameterSetEntry(3) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs index 31b860777ea..5843f25a26b 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimInstanceCommand.cs @@ -29,7 +29,7 @@ public class NewCimInstanceCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public NewCimInstanceCommand() : base(parameters, parameterSets) @@ -57,7 +57,10 @@ public NewCimInstanceCommand() ParameterSetName = CimBaseCommand.ClassNameComputerSet)] public string ClassName { - get { return className; } + get + { + return className; + } set { @@ -82,7 +85,10 @@ public string ClassName ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -117,7 +123,10 @@ public Uri ResourceUri [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Key { - get { return key; } + get + { + return key; + } set { @@ -144,7 +153,10 @@ public string[] Key ParameterSetName = CimClassComputerSet)] public CimClass CimClass { - get { return cimClass; } + get + { + return cimClass; + } set { @@ -169,14 +181,7 @@ public CimClass CimClass ValueFromPipelineByPropertyName = true)] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [Alias("Arguments")] - public IDictionary Property - { - get { return property; } - - set { property = value; } - } - - private IDictionary property; + public IDictionary Property { get; set; } /// /// The following is the definition of the input parameter "Namespace". @@ -197,7 +202,10 @@ public IDictionary Property ParameterSetName = CimBaseCommand.ResourceUriComputerSet)] public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } set { @@ -215,14 +223,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// @@ -245,7 +246,10 @@ public UInt32 OperationTimeoutSec [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -277,7 +281,10 @@ public CimSession[] CimSession [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computerName; } + get + { + return computerName; + } set { @@ -305,7 +312,10 @@ public string[] ComputerName ParameterSetName = CimBaseCommand.CimClassSessionSet)] public SwitchParameter ClientOnly { - get { return clientOnly; } + get + { + return clientOnly; + } set { @@ -367,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 @@ -385,7 +392,7 @@ protected override void EndProcessing() /// private CimNewCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimNewCimInstance); + return this.AsyncOperation as CimNewCimInstance; } /// @@ -397,7 +404,7 @@ private CimNewCimInstance GetOperationAgent() /// private CimNewCimInstance CreateOperationAgent() { - CimNewCimInstance cimNewCimInstance = new CimNewCimInstance(); + CimNewCimInstance cimNewCimInstance = new(); this.AsyncOperation = cimNewCimInstance; return cimNewCimInstance; } @@ -437,7 +444,7 @@ private void CheckArgument() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -500,7 +507,7 @@ private void CheckArgument() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ClassNameSessionSet, new ParameterSetEntry(2) }, { CimBaseCommand.ClassNameComputerSet, new ParameterSetEntry(1, true) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs index eeb4cba4c4d..8b8e36cf829 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionCommand.cs @@ -3,8 +3,8 @@ #region Using directives using System; -using System.Management.Automation; using System.Diagnostics.CodeAnalysis; +using System.Management.Automation; using Microsoft.Management.Infrastructure.Options; #endregion @@ -33,7 +33,10 @@ public sealed class NewCimSessionCommand : CimBaseCommand ParameterSetName = CredentialParameterSet)] public PasswordAuthenticationMechanism Authentication { - get { return authentication; } + get + { + return authentication; + } set { @@ -51,15 +54,8 @@ public PasswordAuthenticationMechanism Authentication /// The default is the current user. /// [Parameter(Position = 1, ParameterSetName = CredentialParameterSet)] - [Credential()] - public PSCredential Credential - { - get { return credential; } - - set { credential = value; } - } - - private PSCredential credential; + [Credential] + public PSCredential Credential { get; set; } /// /// The following is the definition of the input parameter "CertificateThumbprint". @@ -67,14 +63,7 @@ public PSCredential Credential /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = CertificateParameterSet)] - public string CertificateThumbprint - { - get { return certificatethumbprint; } - - set { certificatethumbprint = value; } - } - - private string certificatethumbprint; + public string CertificateThumbprint { get; set; } /// /// The following is the definition of the input parameter "ComputerName". @@ -87,14 +76,7 @@ public string CertificateThumbprint ValueFromPipelineByPropertyName = true)] [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public string[] ComputerName - { - get { return computername; } - - set { computername = value; } - } - - private string[] computername; + public string[] ComputerName { get; set; } /// /// @@ -108,14 +90,7 @@ public string[] ComputerName /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string Name - { - get { return name; } - - set { name = value; } - } - - private string name; + public string Name { get; set; } /// /// @@ -125,13 +100,16 @@ public string Name /// /// /// The unit is Second. - /// + /// /// [Alias(AliasOT)] [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 OperationTimeoutSec + public uint OperationTimeoutSec { - get { return operationTimeout; } + get + { + return operationTimeout; + } set { @@ -140,7 +118,7 @@ public UInt32 OperationTimeoutSec } } - private UInt32 operationTimeout; + private uint operationTimeout; internal bool operationTimeoutSet = false; /// @@ -150,17 +128,7 @@ public UInt32 OperationTimeoutSec /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter SkipTestConnection - { - get { return skipTestConnection; } - - set - { - skipTestConnection = value; - } - } - - private SwitchParameter skipTestConnection; + public SwitchParameter SkipTestConnection { get; set; } /// /// The following is the definition of the input parameter "Port". @@ -168,9 +136,12 @@ public SwitchParameter SkipTestConnection /// This is specificly for wsman protocol. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public UInt32 Port + public uint Port { - get { return port; } + get + { + return port; + } set { @@ -179,7 +150,7 @@ public UInt32 Port } } - private UInt32 port; + private uint port; private bool portSet = false; /// @@ -191,19 +162,14 @@ public UInt32 Port /// If the argument is not given, a default SessionOption will be created for /// the session in .NET API layer. /// + /// /// If a object is passed, then /// connection is made using DCOM. If a /// object is passed, then connection is made using WsMan. + /// /// [Parameter(ValueFromPipelineByPropertyName = true)] - public Microsoft.Management.Infrastructure.Options.CimSessionOptions SessionOption - { - get { return sessionOption; } - - set { sessionOption = value; } - } - - private Microsoft.Management.Infrastructure.Options.CimSessionOptions sessionOption; + public Microsoft.Management.Infrastructure.Options.CimSessionOptions SessionOption { get; set; } #endregion @@ -256,11 +222,11 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr // clone the sessionOption object if (this.SessionOption is WSManSessionOptions) { - options = new WSManSessionOptions(this.sessionOption as WSManSessionOptions); + options = new WSManSessionOptions(this.SessionOption as WSManSessionOptions); } else { - options = new DComSessionOptions(this.sessionOption as DComSessionOptions); + options = new DComSessionOptions(this.SessionOption as DComSessionOptions); } } @@ -268,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; @@ -304,7 +269,7 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr if (this.CertificateThumbprint != null) { - CimCredential credentials = new CimCredential(CertificateAuthenticationMechanism.Default, this.CertificateThumbprint); + CimCredential credentials = new(CertificateAuthenticationMechanism.Default, this.CertificateThumbprint); wsmanOptions.AddDestinationCredentials(credentials); } @@ -319,7 +284,7 @@ internal void BuildSessionOptions(out CimSessionOptions outputOptions, out CimCr } } - if (this.authenticationSet || (this.credential != null)) + if (this.authenticationSet || (this.Credential != null)) { PasswordAuthenticationMechanism authentication = this.authenticationSet ? this.Authentication : PasswordAuthenticationMechanism.Default; if (this.authenticationSet) @@ -368,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 aba99d9401c..54956a9805a 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/NewCimSessionOptionCommand.cs @@ -24,7 +24,7 @@ public enum ProtocolType [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] Wsman - }; + } /// /// The Cmdlet allows the IT Pro to create a CimSessionOptions object that she/he @@ -45,7 +45,7 @@ public sealed class NewCimSessionOptionCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public NewCimSessionOptionCommand() : base(parameters, parameterSets) @@ -64,7 +64,10 @@ public NewCimSessionOptionCommand() [Parameter(ParameterSetName = WSManParameterSet)] public SwitchParameter NoEncryption { - get { return noEncryption; } + get + { + return noEncryption; + } set { @@ -85,7 +88,10 @@ public SwitchParameter NoEncryption ParameterSetName = WSManParameterSet)] public SwitchParameter SkipCACheck { - get { return skipCACheck; } + get + { + return skipCACheck; + } set { @@ -106,7 +112,10 @@ public SwitchParameter SkipCACheck ParameterSetName = WSManParameterSet)] public SwitchParameter SkipCNCheck { - get { return skipCNCheck; } + get + { + return skipCNCheck; + } set { @@ -127,7 +136,10 @@ public SwitchParameter SkipCNCheck ParameterSetName = WSManParameterSet)] public SwitchParameter SkipRevocationCheck { - get { return skipRevocationCheck; } + get + { + return skipRevocationCheck; + } set { @@ -148,7 +160,10 @@ public SwitchParameter SkipRevocationCheck ParameterSetName = WSManParameterSet)] public SwitchParameter EncodePortInServicePrincipalName { - get { return encodeportinserviceprincipalname; } + get + { + return encodeportinserviceprincipalname; + } set { @@ -171,7 +186,10 @@ public SwitchParameter EncodePortInServicePrincipalName ParameterSetName = WSManParameterSet)] public PacketEncoding Encoding { - get { return encoding; } + get + { + return encoding; + } set { @@ -193,7 +211,10 @@ public PacketEncoding Encoding ParameterSetName = WSManParameterSet)] public Uri HttpPrefix { - get { return httpprefix; } + get + { + return httpprefix; + } set { @@ -210,9 +231,12 @@ public Uri HttpPrefix /// [Parameter(ValueFromPipelineByPropertyName = true, ParameterSetName = WSManParameterSet)] - public UInt32 MaxEnvelopeSizeKB + public uint MaxEnvelopeSizeKB { - get { return maxenvelopesizekb; } + get + { + return maxenvelopesizekb; + } set { @@ -222,7 +246,7 @@ public UInt32 MaxEnvelopeSizeKB } } - private UInt32 maxenvelopesizekb; + private uint maxenvelopesizekb; private bool maxenvelopesizekbSet = false; /// @@ -233,7 +257,10 @@ public UInt32 MaxEnvelopeSizeKB ParameterSetName = WSManParameterSet)] public PasswordAuthenticationMechanism ProxyAuthentication { - get { return proxyAuthentication; } + get + { + return proxyAuthentication; + } set { @@ -253,7 +280,10 @@ public PasswordAuthenticationMechanism ProxyAuthentication ParameterSetName = WSManParameterSet)] public string ProxyCertificateThumbprint { - get { return proxycertificatethumbprint; } + get + { + return proxycertificatethumbprint; + } set { @@ -269,10 +299,13 @@ public string ProxyCertificateThumbprint /// Ps Credential used by the proxy server when required by the server. /// [Parameter(ParameterSetName = WSManParameterSet)] - [Credential()] + [Credential] public PSCredential ProxyCredential { - get { return proxycredential; } + get + { + return proxycredential; + } set { @@ -292,7 +325,10 @@ public PSCredential ProxyCredential ParameterSetName = WSManParameterSet)] public ProxyType ProxyType { - get { return proxytype; } + get + { + return proxytype; + } set { @@ -313,7 +349,10 @@ public ProxyType ProxyType ParameterSetName = WSManParameterSet)] public SwitchParameter UseSsl { - get { return usessl; } + get + { + return usessl; + } set { @@ -334,7 +373,10 @@ public SwitchParameter UseSsl [Parameter(ParameterSetName = DcomParameterSet)] public ImpersonationType Impersonation { - get { return impersonation; } + get + { + return impersonation; + } set { @@ -355,7 +397,10 @@ public ImpersonationType Impersonation [Parameter(ParameterSetName = DcomParameterSet)] public SwitchParameter PacketIntegrity { - get { return packetintegrity; } + get + { + return packetintegrity; + } set { @@ -376,7 +421,10 @@ public SwitchParameter PacketIntegrity [Parameter(ParameterSetName = DcomParameterSet)] public SwitchParameter PacketPrivacy { - get { return packetprivacy; } + get + { + return packetprivacy; + } set { @@ -400,7 +448,10 @@ public SwitchParameter PacketPrivacy ParameterSetName = ProtocolNameParameterSet)] public ProtocolType Protocol { - get { return protocol; } + get + { + return protocol; + } set { @@ -416,28 +467,14 @@ public ProtocolType Protocol /// Specifies the UI Culture to use. i.e. en-us, ar-sa. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public CultureInfo UICulture - { - get { return uiculture; } - - set { uiculture = value; } - } - - private CultureInfo uiculture; + public CultureInfo UICulture { get; set; } /// /// The following is the definition of the input parameter "Culture". /// Specifies the culture to use. i.e. en-us, ar-sa. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public CultureInfo Culture - { - get { return culture; } - - set { culture = value; } - } - - private CultureInfo culture; + public CultureInfo Culture { get; set; } #endregion @@ -522,7 +559,7 @@ protected override void EndProcessing() /// internal DComSessionOptions CreateDComSessionOptions() { - DComSessionOptions dcomoptions = new DComSessionOptions(); + DComSessionOptions dcomoptions = new(); if (this.impersonationSet) { dcomoptions.Impersonation = this.Impersonation; @@ -562,7 +599,7 @@ internal DComSessionOptions CreateDComSessionOptions() /// internal WSManSessionOptions CreateWSMANSessionOptions() { - WSManSessionOptions wsmanoptions = new WSManSessionOptions(); + WSManSessionOptions wsmanoptions = new(); if (this.noEncryptionSet) { wsmanoptions.NoEncryption = true; @@ -638,7 +675,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() if (!string.IsNullOrWhiteSpace(this.ProxyCertificateThumbprint)) { - CimCredential credentials = new CimCredential(CertificateAuthenticationMechanism.Default, this.ProxyCertificateThumbprint); + CimCredential credentials = new(CertificateAuthenticationMechanism.Default, this.ProxyCertificateThumbprint); wsmanoptions.AddProxyCredentials(credentials); } @@ -712,7 +749,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameNoEncryption, new HashSet { @@ -813,7 +850,7 @@ internal WSManSessionOptions CreateWSMANSessionOptions() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.ProtocolNameParameterSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.DcomParameterSet, new ParameterSetEntry(0) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs index de7bd63feae..b314691e41f 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RegisterCimIndicationCommand.cs @@ -35,14 +35,7 @@ public class RegisterCimIndicationCommand : ObjectEventRegistrationBase /// /// [Parameter] - public string Namespace - { - get { return nameSpace; } - - set { nameSpace = value; } - } - - private string nameSpace; + public string Namespace { get; set; } /// /// The following is the definition of the input parameter "ClassName". @@ -57,7 +50,10 @@ public string Namespace ParameterSetName = CimBaseCommand.ClassNameComputerSet)] public string ClassName { - get { return className; } + get + { + return className; + } set { @@ -82,7 +78,10 @@ public string ClassName ParameterSetName = CimBaseCommand.QueryExpressionComputerSet)] public string Query { - get { return query; } + get + { + return query; + } set { @@ -104,7 +103,10 @@ public string Query [Parameter(ParameterSetName = CimBaseCommand.QueryExpressionSessionSet)] public string QueryDialect { - get { return queryDialect; } + get + { + return queryDialect; + } set { @@ -122,14 +124,7 @@ public string QueryDialect /// [Alias(CimBaseCommand.AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "Session". @@ -143,7 +138,10 @@ public UInt32 OperationTimeoutSec ParameterSetName = CimBaseCommand.ClassNameSessionSet)] public CimSession CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -164,7 +162,10 @@ public CimSession CimSession [Parameter(ParameterSetName = CimBaseCommand.ClassNameComputerSet)] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -204,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; } @@ -226,10 +227,7 @@ protected override object GetSourceObject() break; } - if (watcher != null) - { - watcher.SetCmdlet(this); - } + watcher?.SetCmdlet(this); return watcher; } @@ -274,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 @@ -292,7 +287,7 @@ private void CheckArgument() /// /// Parameter binder used to resolve parameter set name. /// - private ParameterBinder parameterBinder = new ParameterBinder( + private readonly ParameterBinder parameterBinder = new( parameters, parameterSets); /// @@ -320,7 +315,7 @@ private void SetParameter(object value, string parameterName) /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameClassName, new HashSet { @@ -357,7 +352,7 @@ private void SetParameter(object value, string parameterName) /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.QueryExpressionSessionSet, new ParameterSetEntry(2) }, { CimBaseCommand.QueryExpressionComputerSet, new ParameterSetEntry(1) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs index 60b152055b9..5ac8d129367 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimInstanceCommand.cs @@ -27,7 +27,7 @@ public class RemoveCimInstanceCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public RemoveCimInstanceCommand() : base(parameters, parameterSets) @@ -53,7 +53,10 @@ public RemoveCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -76,7 +79,10 @@ public CimSession[] CimSession ParameterSetName = CimBaseCommand.CimInstanceSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -99,7 +105,10 @@ public Uri ResourceUri [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -124,7 +133,10 @@ public string[] ComputerName ParameterSetName = CimBaseCommand.QueryComputerSet)] public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } set { @@ -142,14 +154,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -168,11 +173,14 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } @@ -180,12 +188,7 @@ public CimInstance InputObject /// /// Property for internal usage purpose. /// - internal CimInstance CimInstance - { - get { return cimInstance; } - } - - private CimInstance cimInstance; + internal CimInstance CimInstance { get; private set; } /// /// The following is the definition of the input parameter "Query". @@ -202,7 +205,10 @@ internal CimInstance CimInstance ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Query { - get { return query; } + get + { + return query; + } set { @@ -224,7 +230,10 @@ public string Query ParameterSetName = CimBaseCommand.QueryComputerSet)] public string QueryDialect { - get { return querydialect; } + get + { + return querydialect; + } set { @@ -267,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 @@ -285,7 +291,7 @@ protected override void EndProcessing() /// private CimRemoveCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimRemoveCimInstance); + return this.AsyncOperation as CimRemoveCimInstance; } /// @@ -297,7 +303,7 @@ private CimRemoveCimInstance GetOperationAgent() /// private CimRemoveCimInstance CreateOperationAgent() { - CimRemoveCimInstance cimRemoveInstance = new CimRemoveCimInstance(); + CimRemoveCimInstance cimRemoveInstance = new(); this.AsyncOperation = cimRemoveInstance; return cimRemoveInstance; } @@ -319,7 +325,7 @@ private CimRemoveCimInstance CreateOperationAgent() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -368,7 +374,7 @@ private CimRemoveCimInstance CreateOperationAgent() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimInstanceComputerSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.CimInstanceSessionSet, new ParameterSetEntry(2) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs index 2fb4126a5b0..2f1a5ad026e 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/RemoveCimSessionCommand.cs @@ -29,7 +29,7 @@ public sealed class RemoveCimSessionCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public RemoveCimSessionCommand() : base(parameters, parameterSets) @@ -54,7 +54,10 @@ public RemoveCimSessionCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimsession; } + get + { + return cimsession; + } set { @@ -81,7 +84,10 @@ public CimSession[] CimSession [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -102,9 +108,12 @@ public string[] ComputerName ValueFromPipelineByPropertyName = true, ParameterSetName = SessionIdSet)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public UInt32[] Id + public uint[] Id { - get { return id; } + get + { + return id; + } set { @@ -113,7 +122,7 @@ public UInt32[] Id } } - private UInt32[] id; + private uint[] id; /// /// The following is the definition of the input parameter "InstanceId". @@ -127,7 +136,10 @@ public UInt32[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Guid[] InstanceId { - get { return instanceid; } + get + { + return instanceid; + } set { @@ -150,7 +162,10 @@ public Guid[] InstanceId [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return name; } + get + { + return name; + } set { @@ -199,7 +214,7 @@ protected override void ProcessRecord() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -231,7 +246,7 @@ protected override void ProcessRecord() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.CimSessionSet, new ParameterSetEntry(1, true) }, { CimBaseCommand.ComputerNameSet, new ParameterSetEntry(1) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs index 7237352b61a..d190e5fafba 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/SetCimInstanceCommand.cs @@ -29,7 +29,7 @@ public class SetCimInstanceCommand : CimBaseCommand #region constructor /// - /// Constructor. + /// Initializes a new instance of the class. /// public SetCimInstanceCommand() : base(parameters, parameterSets) @@ -54,7 +54,10 @@ public SetCimInstanceCommand() [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public CimSession[] CimSession { - get { return cimSession; } + get + { + return cimSession; + } set { @@ -77,7 +80,10 @@ public CimSession[] CimSession [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -100,7 +106,10 @@ public string[] ComputerName ParameterSetName = CimBaseCommand.CimInstanceSessionSet)] public Uri ResourceUri { - get { return resourceUri; } + get + { + return resourceUri; + } set { @@ -121,7 +130,10 @@ public Uri ResourceUri ParameterSetName = CimBaseCommand.QueryComputerSet)] public string Namespace { - get { return nameSpace; } + get + { + return nameSpace; + } set { @@ -139,14 +151,7 @@ public string Namespace /// [Alias(AliasOT)] [Parameter] - public UInt32 OperationTimeoutSec - { - get { return operationTimeout; } - - set { operationTimeout = value; } - } - - private UInt32 operationTimeout; + public uint OperationTimeoutSec { get; set; } /// /// The following is the definition of the input parameter "InputObject". @@ -165,11 +170,14 @@ public UInt32 OperationTimeoutSec [Alias(CimBaseCommand.AliasCimInstance)] public CimInstance InputObject { - get { return cimInstance; } + get + { + return CimInstance; + } set { - cimInstance = value; + CimInstance = value; base.SetParameter(value, nameCimInstance); } } @@ -177,12 +185,7 @@ public CimInstance InputObject /// /// Property for internal usage purpose. /// - internal CimInstance CimInstance - { - get { return cimInstance; } - } - - private CimInstance cimInstance; + internal CimInstance CimInstance { get; private set; } /// /// The following is the definition of the input parameter "Query". @@ -199,7 +202,10 @@ internal CimInstance CimInstance ParameterSetName = CimBaseCommand.QuerySessionSet)] public string Query { - get { return query; } + get + { + return query; + } set { @@ -221,7 +227,10 @@ public string Query ParameterSetName = CimBaseCommand.QueryComputerSet)] public string QueryDialect { - get { return querydialect; } + get + { + return querydialect; + } set { @@ -256,7 +265,10 @@ public string QueryDialect [Alias("Arguments")] public IDictionary Property { - get { return property; } + get + { + return property; + } set { @@ -268,8 +280,10 @@ public IDictionary Property private IDictionary property; /// + /// /// The following is the definition of the input parameter "PassThru", /// indicate whether Set-CimInstance should output modified result instance or not. + /// /// /// True indicates output the result instance, otherwise output nothing as by default /// behavior. @@ -277,20 +291,7 @@ public IDictionary Property /// [Parameter] [ValidateNotNull] - public SwitchParameter PassThru - { - set - { - this.passThru = value; - } - - get - { - return this.passThru; - } - } - - private SwitchParameter passThru; + public SwitchParameter PassThru { get; set; } #endregion @@ -324,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 @@ -342,7 +340,7 @@ protected override void EndProcessing() /// private CimSetCimInstance GetOperationAgent() { - return (this.AsyncOperation as CimSetCimInstance); + return this.AsyncOperation as CimSetCimInstance; } /// @@ -354,7 +352,7 @@ private CimSetCimInstance GetOperationAgent() /// private CimSetCimInstance CreateOperationAgent() { - CimSetCimInstance cimSetCimInstance = new CimSetCimInstance(); + CimSetCimInstance cimSetCimInstance = new(); this.AsyncOperation = cimSetCimInstance; return cimSetCimInstance; } @@ -377,7 +375,7 @@ private CimSetCimInstance CreateOperationAgent() /// /// Static parameter definition entries. /// - private static Dictionary> parameters = new Dictionary> + private static readonly Dictionary> parameters = new() { { nameCimSession, new HashSet { @@ -434,7 +432,7 @@ private CimSetCimInstance CreateOperationAgent() /// /// Static parameter set entries. /// - private static Dictionary parameterSets = new Dictionary + private static readonly Dictionary parameterSets = new() { { CimBaseCommand.QuerySessionSet, new ParameterSetEntry(3) }, { CimBaseCommand.QueryComputerSet, new ParameterSetEntry(2) }, diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs index 10fbc48a444..adcab254231 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/Utils.cs @@ -9,9 +9,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using System.Management.Automation; using System.Text.RegularExpressions; -using System.Threading; namespace Microsoft.Management.Infrastructure.CimCmdlets { @@ -90,7 +88,7 @@ internal static bool IsDefaultComputerName(string computerName) /// internal static IEnumerable GetComputerNames(IEnumerable computerNames) { - return (computerNames == null) ? NullComputerNames : computerNames; + return computerNames ?? NullComputerNames; } /// @@ -112,7 +110,7 @@ internal static string GetComputerName(string computerName) /// internal static string GetNamespace(string nameSpace) { - return (nameSpace == null) ? DefaultNameSpace : nameSpace; + return nameSpace ?? DefaultNameSpace; } /// @@ -124,7 +122,7 @@ internal static string GetNamespace(string nameSpace) /// internal static string GetQueryDialectWithDefault(string queryDialect) { - return (queryDialect == null) ? DefaultQueryDialect : queryDialect; + return queryDialect ?? DefaultQueryDialect; } } @@ -140,31 +138,14 @@ internal static class DebugHelper /// /// Flag used to control generating log message into file. /// - private static bool generateLog = true; - - internal static bool GenerateLog - { - get { return generateLog; } - - set { generateLog = value; } - } + internal static bool GenerateLog { get; set; } = true; /// /// Whether the log been initialized. /// private static bool logInitialized = false; - /// - /// Flag used to control generating message into powershell. - /// - private static bool generateVerboseMessage = true; - - internal static bool GenerateVerboseMessage - { - get { return generateVerboseMessage; } - - set { generateVerboseMessage = value; } - } + internal static bool GenerateVerboseMessage { get; set; } = true; /// /// Flag used to control generating message into powershell. @@ -191,7 +172,7 @@ internal static bool GenerateVerboseMessage /// /// Lock the log file. /// - internal static readonly object logLock = new object(); + internal static readonly object logLock = new(); #endregion @@ -216,20 +197,10 @@ internal static bool GenerateVerboseMessage #region runtime methods internal static string GetSourceCodeInformation(bool withFileName, int depth) { - StackTrace trace = new StackTrace(); + 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 @@ -339,7 +310,7 @@ private static void WriteLogInternal(string message, int indent, int depth) } } - if (generateLog) + if (GenerateLog) { if (indent < 0) { @@ -357,7 +328,7 @@ private static void WriteLogInternal(string message, int indent, int depth) sourceInformation = string.Format( CultureInfo.InvariantCulture, "Thread {0}#{1}:{2}:{3} {4}", - Thread.CurrentThread.ManagedThreadId, + Environment.CurrentManagedThreadId, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second, @@ -366,8 +337,8 @@ private static void WriteLogInternal(string message, int indent, int depth) lock (logLock) { - using (FileStream fs = new FileStream(logFile, FileMode.OpenOrCreate)) - using (StreamWriter writer = new StreamWriter(fs)) + using (FileStream fs = new(logFile, FileMode.OpenOrCreate)) + using (StreamWriter writer = new(fs)) { writer.WriteLineAsync(spaces[indent] + sourceInformation + @" " + message); } @@ -390,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); } /// @@ -425,7 +393,7 @@ public static string ValidateArgumentIsValidName(string parameterName, string va string trimed = value.Trim(); // The first character should be contained in set: [A-Za-z_] // Inner characters should be contained in set: [A-Za-z0-9_] - Regex regex = new Regex(@"^[a-zA-Z_][a-zA-Z0-9_]*\z"); + Regex regex = new(@"^[a-zA-Z_][a-zA-Z0-9_]*\z"); if (regex.IsMatch(trimed)) { DebugHelper.WriteLogEx("A valid name: {0}={1}", 0, parameterName, value); 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 f0308a8c2b7..9cc60c8411c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/Common/Utilities.cs @@ -83,14 +83,11 @@ 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) { - if (!(item is T)) + if (item is not T) { return false; } @@ -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 cb0a79de775..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,30 +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 - { - formatter.Serialize(ms, rule); - - ms.Position = 0; - copy = (FilterRule)formatter.Deserialize(ms); - } - 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..05151330ea2 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"); - } + ArgumentNullException.ThrowIfNull(columns); - if (availableColumns == null) - { - throw new ArgumentNullException("availableColumns"); - } + 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 466511d6d22..acc4eae2c61 100644 --- a/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj +++ b/src/Microsoft.Management.UI.Internal/Microsoft.PowerShell.GraphicalHost.csproj @@ -1,10 +1,14 @@ - + Assembly containing WPF code for Out-GridView, HelpWindow, and Show-Command $(NoWarn);CS1570 Microsoft.Management.UI.Internal Microsoft.PowerShell.GraphicalHost + false + Windows + 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 c258a2819ac..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, @@ -357,7 +354,7 @@ private RowDefinition CreateNewRow() /// Will adding UIControl. private void AddControlToMainGrid(UIElement uiControl) { - if (this.firstFocusableElement == null && !(uiControl is Label)) + if (this.firstFocusableElement == null && uiControl is not Label) { this.firstFocusableElement = uiControl; } @@ -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..10690b65dc7 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. @@ -710,7 +679,8 @@ internal static string SingleQuote(string str) /// The host window, if it is present or null if it is not. internal static Window GetHostWindow(PSCmdlet cmdlet) { - PSPropertyInfo windowProperty = cmdlet.Host.PrivateData.Properties["Window"]; + // The value of 'PrivateData' property may be null for the default host or a custom host. + PSPropertyInfo windowProperty = cmdlet.Host.PrivateData?.Properties["Window"]; if (windowProperty == null) { return null; @@ -750,7 +720,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 +764,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 +970,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 +1020,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 +1170,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 bd99ef7aff2..dfe046ec8ca 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CommonUtils.cs @@ -2,9 +2,7 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Diagnostics; -using System.Globalization; using System.Reflection; using System.Resources; using System.Runtime.InteropServices; @@ -42,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) @@ -57,7 +55,6 @@ public static uint FormatMessageFromModule(uint lastError, string moduleName, ou try { - uint dwFormatFlags = FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE; uint LANGID = (uint)GetUserDefaultLangID(); uint langError = (uint)Marshal.GetLastWin32Error(); if (langError != 0) @@ -65,14 +62,15 @@ public static uint FormatMessageFromModule(uint lastError, string moduleName, ou LANGID = 0; // neutral } - StringBuilder outStringBuilder = new StringBuilder(1024); - uint nChars = FormatMessage(dwFormatFlags, - moduleHandle, - lastError, - LANGID, - outStringBuilder, - (uint)outStringBuilder.Capacity, - IntPtr.Zero); + StringBuilder outStringBuilder = new(1024); + uint nChars = FormatMessage( + dwFlags: FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE, + lpSource: moduleHandle, + dwMessageId: lastError, + dwLanguageId: LANGID, + lpBuffer: outStringBuilder, + nSize: (uint)outStringBuilder.Capacity, + Arguments: IntPtr.Zero); if (nChars == 0) { @@ -100,4 +98,3 @@ public static ResourceManager GetResourceManager() } } } - 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 00981ad8847..00000000000 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CoreCLR/Stubs.cs +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if CORECLR -using System.ComponentModel; - -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/CounterFileInfo.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs index cbebb9b4557..2b11b6d3b03 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs @@ -54,4 +54,3 @@ public UInt32 SampleCount private UInt32 _sampleCount = 0; } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs index 3a7af308f29..9089216a485 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSample.cs @@ -2,16 +2,10 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Reflection; using System.Resources; -using Microsoft.Powershell.Commands.GetCounter.PdhNative; - namespace Microsoft.PowerShell.Commands.GetCounter { public class PerformanceCounterSample @@ -33,127 +27,43 @@ internal PerformanceCounterSample(string path, UInt64 timeStamp100nSec, UInt32 status) { - _path = path; - _instanceName = instanceName; - _cookedValue = cookedValue; - _rawValue = rawValue; - _secondValue = secondValue; - _multiCount = multiCount; - _counterType = counterType; - _defaultScale = defaultScale; - _timeBase = timeBase; - _timeStamp = timeStamp; - _timeStamp100nSec = timeStamp100nSec; - _status = status; + Path = path; + InstanceName = instanceName; + CookedValue = cookedValue; + RawValue = rawValue; + SecondValue = secondValue; + MultipleCount = multiCount; + CounterType = counterType; + DefaultScale = defaultScale; + TimeBase = timeBase; + Timestamp = timeStamp; + Timestamp100NSec = timeStamp100nSec; + Status = status; } - public string Path - { - get { return _path; } + public string Path { get; set; } = string.Empty; - set { _path = value; } - } + public string InstanceName { get; set; } = string.Empty; - private string _path = string.Empty; + public double CookedValue { get; set; } - public string InstanceName - { - get { return _instanceName; } + public UInt64 RawValue { get; set; } - set { _instanceName = value; } - } + public UInt64 SecondValue { get; set; } - private string _instanceName = string.Empty; + public uint MultipleCount { get; set; } - public double CookedValue - { - get { return _cookedValue; } + public PerformanceCounterType CounterType { get; set; } - set { _cookedValue = value; } - } + public DateTime Timestamp { get; set; } = DateTime.MinValue; - private double _cookedValue = 0; + public UInt64 Timestamp100NSec { get; set; } - public UInt64 RawValue - { - get { return _rawValue; } - - set { _rawValue = value; } - } - - private UInt64 _rawValue = 0; - - public UInt64 SecondValue - { - get { return _secondValue; } - - set { _secondValue = value; } - } + public UInt32 Status { get; set; } - private UInt64 _secondValue = 0; - - public uint MultipleCount - { - get { return _multiCount; } - - set { _multiCount = value; } - } - - private uint _multiCount = 0; - - public PerformanceCounterType CounterType - { - get { return _counterType; } - - set { _counterType = value; } - } - - private PerformanceCounterType _counterType = 0; - - public DateTime Timestamp - { - get { return _timeStamp; } - - set { _timeStamp = value; } - } + public UInt32 DefaultScale { get; set; } - private DateTime _timeStamp = DateTime.MinValue; - - public UInt64 Timestamp100NSec - { - get { return _timeStamp100nSec; } - - set { _timeStamp100nSec = value; } - } - - private UInt64 _timeStamp100nSec = 0; - - public UInt32 Status - { - get { return _status; } - - set { _status = value; } - } - - private UInt32 _status = 0; - - public UInt32 DefaultScale - { - get { return _defaultScale; } - - set { _defaultScale = value; } - } - - private UInt32 _defaultScale = 0; - - public UInt64 TimeBase - { - get { return _timeBase; } - - set { _timeBase = value; } - } - - private UInt64 _timeBase = 0; + public UInt64 TimeBase { get; set; } } public class PerformanceCounterSampleSet @@ -167,32 +77,18 @@ internal PerformanceCounterSampleSet(DateTime timeStamp, PerformanceCounterSample[] counterSamples, bool firstSet) : this() { - _timeStamp = timeStamp; - _counterSamples = counterSamples; - } - - public DateTime Timestamp - { - get { return _timeStamp; } - - set { _timeStamp = value; } + Timestamp = timeStamp; + CounterSamples = counterSamples; } - private DateTime _timeStamp = DateTime.MinValue; + public DateTime Timestamp { get; set; } = DateTime.MinValue; [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.GetCounter.PerformanceCounterSample.CounterSamples", Justification = "A string[] is required here because that is the type Powershell supports")] - public PerformanceCounterSample[] CounterSamples - { - get { return _counterSamples; } - - set { _counterSamples = value; } - } - - private PerformanceCounterSample[] _counterSamples = null; + public PerformanceCounterSample[] CounterSamples { get; set; } - private ResourceManager _resourceMgr = null; + private readonly ResourceManager _resourceMgr = null; } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs index c1474ec4209..dfc175b604c 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterSet.cs @@ -2,10 +2,8 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.ComponentModel; using System.Diagnostics; namespace Microsoft.PowerShell.Commands.GetCounter @@ -18,94 +16,54 @@ internal CounterSet(string setName, string setHelp, ref Dictionary counterInstanceMapping) { - _counterSetName = setName; + CounterSetName = setName; if (machineName == null || machineName.Length == 0) { machineName = "."; } else { - _machineName = machineName; - if (!_machineName.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)) + MachineName = machineName; + if (!MachineName.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase)) { - _machineName = @"\\" + _machineName; + MachineName = @"\\" + MachineName; } } - _counterSetType = categoryType; - _description = setHelp; - _counterInstanceMapping = counterInstanceMapping; + CounterSetType = categoryType; + Description = setHelp; + CounterInstanceMapping = counterInstanceMapping; } - public string CounterSetName - { - get - { - return _counterSetName; - } - } - - private string _counterSetName = string.Empty; - - public string MachineName - { - get - { - return _machineName; - } - } - - private string _machineName = "."; - - public PerformanceCounterCategoryType CounterSetType - { - get - { - return _counterSetType; - } - } + public string CounterSetName { get; } = string.Empty; - private PerformanceCounterCategoryType _counterSetType; + public string MachineName { get; } = "."; - public string Description - { - get - { - return _description; - } - } - - private string _description = string.Empty; + public PerformanceCounterCategoryType CounterSetType { get; } - internal Dictionary CounterInstanceMapping - { - get - { - return _counterInstanceMapping; - } - } + public string Description { get; } = string.Empty; - private Dictionary _counterInstanceMapping; + internal Dictionary CounterInstanceMapping { get; } public StringCollection Paths { get { - StringCollection retColl = new StringCollection(); + StringCollection retColl = new(); foreach (string counterName in this.CounterInstanceMapping.Keys) { string path; if (CounterInstanceMapping[counterName].Length != 0) { - path = (_machineName == ".") ? - ("\\" + _counterSetName + "(*)\\" + counterName) : - (_machineName + "\\" + _counterSetName + "(*)\\" + counterName); + path = (MachineName == ".") ? + ("\\" + CounterSetName + "(*)\\" + counterName) : + (MachineName + "\\" + CounterSetName + "(*)\\" + counterName); } else { - path = (_machineName == ".") ? - ("\\" + _counterSetName + "\\" + counterName) : - (_machineName + "\\" + _counterSetName + "\\" + counterName); + path = (MachineName == ".") ? + ("\\" + CounterSetName + "\\" + counterName) : + (MachineName + "\\" + CounterSetName + "\\" + counterName); } retColl.Add(path); @@ -119,14 +77,14 @@ public StringCollection PathsWithInstances { get { - StringCollection retColl = new StringCollection(); + StringCollection retColl = new(); foreach (string counterName in CounterInstanceMapping.Keys) { foreach (string instanceName in CounterInstanceMapping[counterName]) { - string path = (_machineName == ".") ? - ("\\" + _counterSetName + "(" + instanceName + ")\\" + counterName) : - (_machineName + "\\" + _counterSetName + "(" + instanceName + ")\\" + counterName); + string path = (MachineName == ".") ? + ("\\" + CounterSetName + "(" + instanceName + ")\\" + counterName) : + (MachineName + "\\" + CounterSetName + "(" + instanceName + ")\\" + counterName); retColl.Add(path); } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs index 459d553e27a..0122ad1941f 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetCounterCommand.cs @@ -2,35 +2,23 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -using System.Reflection; using System.Resources; -using System.Security; -using System.Security.Principal; -using System.Text; using System.Threading; -using System.Xml; - +using Microsoft.Powershell.Commands.GetCounter.PdhNative; using Microsoft.PowerShell.Commands.Diagnostics.Common; using Microsoft.PowerShell.Commands.GetCounter; -using Microsoft.Powershell.Commands.GetCounter.PdhNative; namespace Microsoft.PowerShell.Commands { - /// + /// /// Class that implements the Get-Counter cmdlet. - /// + /// [Cmdlet(VerbsCommon.Get, "Counter", DefaultParameterSetName = "GetCounterSet", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2109647")] public sealed class GetCounterCommand : PSCmdlet { @@ -49,15 +37,7 @@ public sealed class GetCounterCommand : PSCmdlet Scope = "member", Target = "Microsoft.PowerShell.Commands.GetCounterCommand.ListSet", Justification = "A string[] is required here because that is the type Powershell supports")] - - public string[] ListSet - { - get { return _listSet; } - - set { _listSet = value; } - } - - private string[] _listSet = { "*" }; + public string[] ListSet { get; set; } = { "*" }; // // Counter parameter @@ -75,7 +55,10 @@ public string[] ListSet Justification = "A string[] is required here because that is the type Powershell supports")] public string[] Counter { - get { return _counter; } + get + { + return _counter; + } set { @@ -93,7 +76,7 @@ public string[] Counter private bool _defaultCounters = true; - private List _accumulatedCounters = new List(); + private readonly List _accumulatedCounters = new(); // // SampleInterval parameter. @@ -105,14 +88,7 @@ public string[] Counter ValueFromPipelineByPropertyName = false, HelpMessageBaseName = "GetEventResources")] [ValidateRange((int)1, int.MaxValue)] - public int SampleInterval - { - get { return _sampleInterval; } - - set { _sampleInterval = value; } - } - - private int _sampleInterval = 1; + public int SampleInterval { get; set; } = 1; // // MaxSamples parameter @@ -127,7 +103,10 @@ public int SampleInterval [ValidateRange((Int64)1, Int64.MaxValue)] public Int64 MaxSamples { - get { return _maxSamples; } + get + { + return _maxSamples; + } set { @@ -167,20 +146,13 @@ public SwitchParameter Continuous Scope = "member", Target = "Microsoft.PowerShell.Commands.GetCounterCommand.ComputerName", Justification = "A string[] is required here because that is the type Powershell supports")] - public string[] ComputerName - { - get { return _computerName; } - - set { _computerName = value; } - } - - private string[] _computerName = Array.Empty(); + public string[] ComputerName { get; set; } = Array.Empty(); private ResourceManager _resourceMgr = null; private PdhHelper _pdhHelper = null; - private EventWaitHandle _cancelEventArrived = new EventWaitHandle(false, EventResetMode.ManualReset); + private readonly EventWaitHandle _cancelEventArrived = new(false, EventResetMode.ManualReset); // Culture identifier(s) private const string FrenchCultureId = "fr-FR"; @@ -192,17 +164,17 @@ public string[] ComputerName // // With this dictionary, we can add special mapping if we find other special cases in the future. private readonly Dictionary>> _cultureAndSpecialCharacterMap = - new Dictionary>>() + new() { { FrenchCultureId, new List>() { // 'APOSTROPHE' to 'RIGHT SINGLE QUOTATION MARK' - new Tuple((char) 0x0027, (char) 0x2019), + new Tuple((char)0x0027, (char)0x2019), // 'MODIFIER LETTER APOSTROPHE' to 'RIGHT SINGLE QUOTATION MARK' - new Tuple((char) 0x02BC, (char) 0x2019), + new Tuple((char)0x02BC, (char)0x2019), // 'HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT' to 'RIGHT SINGLE QUOTATION MARK' - new Tuple((char) 0x275C, (char) 0x2019), + new Tuple((char)0x275C, (char)0x2019), } } }; @@ -222,7 +194,7 @@ protected override void BeginProcessing() _resourceMgr = Microsoft.PowerShell.Commands.Diagnostics.Common.CommonUtilities.GetResourceManager(); uint res = _pdhHelper.ConnectToDataSource(); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { ReportPdhError(res, true); return; @@ -230,7 +202,7 @@ protected override void BeginProcessing() if (Continuous.IsPresent && _maxSamplesSpecified) { - Exception exc = new Exception(string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterContinuousOrMaxSamples"))); + Exception exc = new(string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterContinuousOrMaxSamples"))); ThrowTerminatingError(new ErrorRecord(exc, "CounterContinuousOrMaxSamples", ErrorCategory.InvalidArgument, null)); } } @@ -276,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; } } @@ -300,12 +272,12 @@ private void AccumulatePipelineCounters() // private void ProcessListSet() { - if (_computerName.Length == 0) + if (ComputerName.Length == 0) { ProcessListSetPerMachine(null); } else - foreach (string machine in _computerName) + foreach (string machine in ComputerName) { ProcessListSetPerMachine(machine); } @@ -317,24 +289,24 @@ private void ProcessListSet() // private void ProcessListSetPerMachine(string machine) { - StringCollection counterSets = new StringCollection(); + StringCollection counterSets = new(); uint res = _pdhHelper.EnumObjects(machine, ref counterSets); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // add an error message string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("NoCounterSetsOnComputer"), machine, res); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "NoCounterSetsOnComputer", ErrorCategory.InvalidResult, machine)); return; } CultureInfo culture = GetCurrentCulture(); List> characterReplacementList = null; - StringCollection validPaths = new StringCollection(); + StringCollection validPaths = new(); _cultureAndSpecialCharacterMap.TryGetValue(culture.Name, out characterReplacementList); - foreach (string pattern in _listSet) + foreach (string pattern in ListSet) { bool bMatched = false; string normalizedPattern = pattern; @@ -347,7 +319,7 @@ private void ProcessListSetPerMachine(string machine) } } - WildcardPattern wildLogPattern = new WildcardPattern(normalizedPattern, WildcardOptions.IgnoreCase); + WildcardPattern wildLogPattern = new(normalizedPattern, WildcardOptions.IgnoreCase); foreach (string counterSet in counterSets) { @@ -356,18 +328,18 @@ private void ProcessListSetPerMachine(string machine) continue; } - StringCollection counterSetCounters = new StringCollection(); - StringCollection counterSetInstances = new StringCollection(); + StringCollection counterSetCounters = new(); + StringCollection counterSetInstances = new(); res = _pdhHelper.EnumObjectItems(machine, counterSet, ref counterSetCounters, ref counterSetInstances); if (res == PdhResults.PDH_ACCESS_DENIED) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterSetEnumAccessDenied"), counterSet); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "CounterSetEnumAccessDenied", ErrorCategory.InvalidResult, null)); continue; } - else if (res != 0) + else if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { ReportPdhError(res, false); continue; @@ -389,7 +361,7 @@ private void ProcessListSetPerMachine(string machine) instanceArray[0] = "*"; } - Dictionary counterInstanceMapping = new Dictionary(); + Dictionary counterInstanceMapping = new(); foreach (string counter in counterSetCounters) { counterInstanceMapping.TryAdd(counter, instanceArray); @@ -407,7 +379,7 @@ private void ProcessListSetPerMachine(string machine) string setHelp = _pdhHelper.GetCounterSetHelp(machine, counterSet); - CounterSet setObj = new CounterSet(counterSet, machine, categoryType, setHelp, ref counterInstanceMapping); + CounterSet setObj = new(counterSet, machine, categoryType, setHelp, ref counterInstanceMapping); WriteObject(setObj); bMatched = true; } @@ -415,7 +387,7 @@ private void ProcessListSetPerMachine(string machine) if (!bMatched) { string msg = _resourceMgr.GetString("NoMatchingCounterSetsFound"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, machine ?? "localhost", normalizedPattern)); WriteError(new ErrorRecord(exc, "NoMatchingCounterSetsFound", ErrorCategory.ObjectNotFound, null)); } @@ -437,24 +409,24 @@ private void ProcessGetCounter() CultureInfo culture = GetCurrentCulture(); List> characterReplacementList = null; List paths = CombineMachinesAndCounterPaths(); - uint res = 0; if (!_defaultCounters) { _cultureAndSpecialCharacterMap.TryGetValue(culture.Name, out characterReplacementList); } - StringCollection allExpandedPaths = new StringCollection(); + StringCollection allExpandedPaths = new(); + uint res; foreach (string path in paths) { string localizedPath = path; if (_defaultCounters) { res = _pdhHelper.TranslateLocalCounterPath(path, out localizedPath); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { string msg = string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterPathTranslationFailed"), res); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "CounterPathTranslationFailed", ErrorCategory.InvalidResult, null)); localizedPath = path; @@ -470,7 +442,7 @@ private void ProcessGetCounter() StringCollection expandedPaths; res = _pdhHelper.ExpandWildCardPath(localizedPath, out expandedPaths); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { WriteDebug("Could not expand path " + localizedPath); ReportPdhError(res, false); @@ -482,7 +454,7 @@ private void ProcessGetCounter() if (!_pdhHelper.IsPathValid(expandedPath)) { string msg = string.Format(CultureInfo.CurrentCulture, _resourceMgr.GetString("CounterPathIsInvalid"), localizedPath); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "CounterPathIsInvalid", ErrorCategory.InvalidResult, null)); continue; @@ -498,13 +470,13 @@ private void ProcessGetCounter() } res = _pdhHelper.OpenQuery(); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { ReportPdhError(res, false); } res = _pdhHelper.AddCounters(ref allExpandedPaths, true); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { ReportPdhError(res, true); @@ -526,7 +498,7 @@ private void ProcessGetCounter() // read the first set just to get the initial values res = _pdhHelper.ReadNextSet(out nextSet, bSkip); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { // Display valid data if (!bSkip) @@ -562,7 +534,7 @@ private void ProcessGetCounter() break; } - bool cancelled = _cancelEventArrived.WaitOne((int)_sampleInterval * 1000, true); + bool cancelled = _cancelEventArrived.WaitOne((int)SampleInterval * 1000, true); if (cancelled) { break; @@ -579,7 +551,7 @@ private void ReportPdhError(uint res, bool bTerminate) msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterApiError"), res); } - Exception exc = new Exception(msg); + Exception exc = new(msg); if (bTerminate) { ThrowTerminatingError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null)); @@ -597,9 +569,9 @@ private void ReportPdhError(uint res, bool bTerminate) // private List CombineMachinesAndCounterPaths() { - List retColl = new List(); + List retColl = new(); - if (_computerName.Length == 0) + if (ComputerName.Length == 0) { retColl.AddRange(_accumulatedCounters); return retColl; @@ -613,15 +585,16 @@ private List CombineMachinesAndCounterPaths() } else { - foreach (string machine in _computerName) + 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); } } } @@ -642,7 +615,7 @@ private void WriteSampleSetObject(PerformanceCounterSampleSet set) if (sample.Status != 0) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("CounterSampleDataInvalid")); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "CounterApiError", ErrorCategory.InvalidResult, null)); break; } @@ -657,4 +630,3 @@ private static CultureInfo GetCurrentCulture() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs index 33e127565e6..0ce68a31d73 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs @@ -11,7 +11,6 @@ using System.Globalization; using System.Management.Automation; using System.Net; -using System.Reflection; using System.Resources; using System.Security.Principal; using System.Text; @@ -21,9 +20,12 @@ 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 { @@ -43,15 +45,7 @@ public sealed class GetWinEventCommand : PSCmdlet Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.ListLog", Justification = "A string[] is required here because that is the type Powershell supports")] - - public string[] ListLog - { - get { return _listLog; } - - set { _listLog = value; } - } - - private string[] _listLog = { "*" }; + public string[] ListLog { get; set; } = { "*" }; /// /// GetLog parameter. @@ -67,14 +61,7 @@ public string[] ListLog Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.LogName", Justification = "A string[] is required here because that is the type Powershell supports")] - public string[] LogName - { - get { return _logName; } - - set { _logName = value; } - } - - private string[] _logName = { "*" }; + public string[] LogName { get; set; } = { "*" }; /// /// ListProvider parameter. @@ -88,20 +75,11 @@ public string[] LogName HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "ListProviderParamHelp")] [AllowEmptyCollection] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.ListProvider", Justification = "A string[] is required here because that is the type Powershell supports")] - - public string[] ListProvider - { - get { return _listProvider; } - - set { _listProvider = value; } - } - - private string[] _listProvider = { "*" }; + public string[] ListProvider { get; set; } = { "*" }; /// /// ProviderName parameter. @@ -113,20 +91,11 @@ public string[] ListProvider ValueFromPipelineByPropertyName = true, HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "GetProviderParamHelp")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.ProviderName", Justification = "A string[] is required here because that is the type Powershell supports")] - - public string[] ProviderName - { - get { return _providerName; } - - set { _providerName = value; } - } - - private string[] _providerName; + public string[] ProviderName { get; set; } /// /// Path parameter. @@ -138,20 +107,12 @@ public string[] ProviderName ValueFromPipelineByPropertyName = true, HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "PathParamHelp")] - [Alias("PSPath")] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.Path", Justification = "A string[] is required here because that is the type Powershell supports")] - public string[] Path - { - get { return _path; } - - set { _path = value; } - } - - private string[] _path; + public string[] Path { get; set; } /// /// MaxEvents parameter. @@ -186,15 +147,8 @@ public string[] Path ValueFromPipelineByPropertyName = false, HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "MaxEventsParamHelp")] - [ValidateRange((Int64)1, Int64.MaxValue)] - public Int64 MaxEvents - { - get { return _maxEvents; } - - set { _maxEvents = value; } - } - - private Int64 _maxEvents = -1; + [ValidateRange((long)1, long.MaxValue)] + public long MaxEvents { get; set; } = -1; /// /// ComputerName parameter. @@ -223,17 +177,9 @@ public Int64 MaxEvents ParameterSetName = "XmlQuerySet", HelpMessageBaseName = "GetEventResources", HelpMessageResourceId = "ComputerNameParamHelp")] - [ValidateNotNull] [Alias("Cn")] - public string ComputerName - { - get { return _computerName; } - - set { _computerName = value; } - } - - private string _computerName = string.Empty; + public string ComputerName { get; set; } = string.Empty; /// /// Credential parameter. @@ -246,14 +192,7 @@ public string ComputerName [Parameter(ParameterSetName = "XmlQuerySet")] [Parameter(ParameterSetName = "FileSet")] [Credential] - public PSCredential Credential - { - get { return _credential; } - - set { _credential = value; } - } - - private PSCredential _credential = PSCredential.Empty; + public PSCredential Credential { get; set; } = PSCredential.Empty; /// /// FilterXPath parameter. @@ -274,14 +213,7 @@ public PSCredential Credential ValueFromPipelineByPropertyName = false, HelpMessageBaseName = "GetEventResources")] [ValidateNotNull] - public string FilterXPath - { - get { return _filter; } - - set { _filter = value; } - } - - private string _filter = "*"; + public string FilterXPath { get; set; } = "*"; /// /// FilterXml parameter. @@ -293,20 +225,7 @@ public string FilterXPath ValueFromPipelineByPropertyName = false, ParameterSetName = "XmlQuerySet", HelpMessageBaseName = "GetEventResources")] - - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", - Scope = "member", - Target = "Microsoft.PowerShell.Commands.GetEvent.FilterXml", - Justification = "An XmlDocument is required here because that is the type Powershell supports")] - - public XmlDocument FilterXml - { - get { return _xmlQuery; } - - set { _xmlQuery = value; } - } - - private XmlDocument _xmlQuery = null; + public XmlDocument FilterXml { get; set; } /// /// FilterHashtable parameter. @@ -318,20 +237,11 @@ public XmlDocument FilterXml ValueFromPipelineByPropertyName = false, ParameterSetName = "HashQuerySet", HelpMessageBaseName = "GetEventResources")] - [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Scope = "member", Target = "Microsoft.PowerShell.Commands.GetEvent.FilterHashtable", Justification = "A string[] is required here because that is the type Powershell supports")] - - public Hashtable[] FilterHashtable - { - get { return _selector; } - - set { _selector = value; } - } - - private Hashtable[] _selector; + public Hashtable[] FilterHashtable { get; set; } /// /// Force switch. @@ -339,16 +249,8 @@ public Hashtable[] FilterHashtable [Parameter(ParameterSetName = "ListLogSet")] [Parameter(ParameterSetName = "GetProviderSet")] [Parameter(ParameterSetName = "GetLogSet")] - [Parameter(ParameterSetName = "HashQuerySet")] - public SwitchParameter Force - { - get { return _force; } - - set { _force = value; } - } - - private SwitchParameter _force; + public SwitchParameter Force { get; set; } /// /// Oldest switch. @@ -356,7 +258,6 @@ public SwitchParameter Force [Parameter(ParameterSetName = "FileSet")] [Parameter(ParameterSetName = "GetProviderSet")] [Parameter(ParameterSetName = "GetLogSet")] - [Parameter(ParameterSetName = "HashQuerySet")] [Parameter(ParameterSetName = "XmlQuerySet")] public SwitchParameter Oldest @@ -379,8 +280,8 @@ public SwitchParameter Oldest private const string SelectCloser = ""; private const string suppressOpener = "*"; private const string suppressCloser = ""; - private const string propOpen = "["; - private const string propClose = "]"; + private const char propOpen = '['; + private const char propClose = ']'; private const string filePrefix = "file://"; private const string NamedDataTemplate = "((EventData[Data[@Name='{0}']='{1}']) or (UserData/*/{0}='{1}'))"; private const string DataTemplate = "(EventData/Data='{0}')"; @@ -396,14 +297,14 @@ public SwitchParameter Oldest // Other private members and constants // private ResourceManager _resourceMgr = null; - private Dictionary _providersByLogMap = new Dictionary(); + private readonly Dictionary _providersByLogMap = new(); private StringCollection _logNamesMatchingWildcard = null; - private StringCollection _resolvedPaths = new StringCollection(); + private readonly StringCollection _resolvedPaths = new(); - private List _accumulatedLogNames = new List(); - private List _accumulatedProviderNames = new List(); - private List _accumulatedFileNames = new List(); + private readonly List _accumulatedLogNames = new(); + private readonly List _accumulatedProviderNames = new(); + private readonly List _accumulatedFileNames = new(); private const uint MAX_EVENT_BATCH = 100; @@ -491,7 +392,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; } } @@ -502,7 +403,7 @@ protected override void ProcessRecord() // private void AccumulatePipelineLogNames() { - _accumulatedLogNames.AddRange(_logName); + _accumulatedLogNames.AddRange(LogName); } // @@ -511,7 +412,7 @@ private void AccumulatePipelineLogNames() // private void AccumulatePipelineProviderNames() { - _accumulatedProviderNames.AddRange(_logName); + _accumulatedProviderNames.AddRange(LogName); } // @@ -520,7 +421,7 @@ private void AccumulatePipelineProviderNames() // private void AccumulatePipelineFileNames() { - _accumulatedFileNames.AddRange(_logName); + _accumulatedFileNames.AddRange(LogName); } // @@ -545,7 +446,7 @@ private void ProcessGetLog() } else { - logQuery = new EventLogQuery(_logNamesMatchingWildcard[0], PathType.LogName, _filter); + logQuery = new EventLogQuery(_logNamesMatchingWildcard[0], PathType.LogName, FilterXPath); } logQuery.Session = eventLogSession; @@ -562,7 +463,7 @@ private void ProcessGetProvider() { using (EventLogSession eventLogSession = CreateSession()) { - FindProvidersByLogForWildcardPatterns(eventLogSession, _providerName); + FindProvidersByLogForWildcardPatterns(eventLogSession, ProviderName); if (_providersByLogMap.Count == 0) { @@ -587,7 +488,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")); } } @@ -605,10 +506,10 @@ private void ProcessListLog() { using (EventLogSession eventLogSession = CreateSession()) { - foreach (string logPattern in _listLog) + foreach (string logPattern in ListLog) { bool bMatchFound = false; - WildcardPattern wildLogPattern = new WildcardPattern(logPattern, WildcardOptions.IgnoreCase); + WildcardPattern wildLogPattern = new(logPattern, WildcardOptions.IgnoreCase); foreach (string logName in eventLogSession.GetLogNames()) { @@ -617,9 +518,11 @@ private void ProcessListLog() || (wildLogPattern.IsMatch(logName))) { + EventLogConfiguration logObj; + EventLogInformation logInfoObj; try { - EventLogConfiguration logObj = new EventLogConfiguration(logName, eventLogSession); + logObj = new EventLogConfiguration(logName, eventLogSession); // // Skip direct channels matching the wildcard unless -Force is present. @@ -632,36 +535,52 @@ private void ProcessListLog() continue; } - EventLogInformation logInfoObj = eventLogSession.GetLogInformation(logName, PathType.LogName); - - PSObject outputObj = new PSObject(logObj); + bMatchFound = true; + logInfoObj = eventLogSession.GetLogInformation(logName, PathType.LogName); + } + catch (UnauthorizedAccessException exc) + { + string exceptionMsg = string.Format(CultureInfo.InvariantCulture, GetEventResources.LogInfoNoAccess, logName); + var newExc = new UnauthorizedAccessException(exceptionMsg, exc); - outputObj.Properties.Add(new PSNoteProperty("FileSize", logInfoObj.FileSize)); - outputObj.Properties.Add(new PSNoteProperty("IsLogFull", logInfoObj.IsLogFull)); - outputObj.Properties.Add(new PSNoteProperty("LastAccessTime", logInfoObj.LastAccessTime)); - outputObj.Properties.Add(new PSNoteProperty("LastWriteTime", logInfoObj.LastWriteTime)); - outputObj.Properties.Add(new PSNoteProperty("OldestRecordNumber", logInfoObj.OldestRecordNumber)); - outputObj.Properties.Add(new PSNoteProperty("RecordCount", logInfoObj.RecordCount)); + string recommendationMsg = GetEventResources.SuggestElevation; + var eRecord = new ErrorRecord(newExc, "LogInfoNoAccess", ErrorCategory.PermissionDenied, logName) + { + ErrorDetails = new ErrorDetails(string.Empty) + { + RecommendedAction = recommendationMsg + } + }; - WriteObject(outputObj); - bMatchFound = true; + WriteError(eRecord); + continue; } catch (Exception exc) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("LogInfoUnavailable"), logName, exc.Message); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "LogInfoUnavailable", ErrorCategory.NotSpecified, null)); continue; } + + PSObject outputObj = new(logObj); + outputObj.Properties.Add(new PSNoteProperty("FileSize", logInfoObj.FileSize)); + outputObj.Properties.Add(new PSNoteProperty("IsLogFull", logInfoObj.IsLogFull)); + outputObj.Properties.Add(new PSNoteProperty("LastAccessTime", logInfoObj.LastAccessTime)); + outputObj.Properties.Add(new PSNoteProperty("LastWriteTime", logInfoObj.LastWriteTime)); + outputObj.Properties.Add(new PSNoteProperty("OldestRecordNumber", logInfoObj.OldestRecordNumber)); + outputObj.Properties.Add(new PSNoteProperty("RecordCount", logInfoObj.RecordCount)); + + WriteObject(outputObj); } } if (!bMatchFound) { string msg = _resourceMgr.GetString("NoMatchingLogsFound"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, _computerName, logPattern)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, ComputerName, logPattern)); WriteError(new ErrorRecord(exc, "NoMatchingLogsFound", ErrorCategory.ObjectNotFound, null)); } } @@ -675,10 +594,10 @@ private void ProcessListProvider() { using (EventLogSession eventLogSession = CreateSession()) { - foreach (string provPattern in _listProvider) + foreach (string provPattern in ListProvider) { bool bMatchFound = false; - WildcardPattern wildProvPattern = new WildcardPattern(provPattern, WildcardOptions.IgnoreCase); + WildcardPattern wildProvPattern = new(provPattern, WildcardOptions.IgnoreCase); foreach (string provName in eventLogSession.GetProviderNames()) { @@ -689,7 +608,7 @@ private void ProcessListProvider() { try { - ProviderMetadata provObj = new ProviderMetadata(provName, eventLogSession, CultureInfo.CurrentCulture); + ProviderMetadata provObj = new(provName, eventLogSession, CultureInfo.CurrentCulture); WriteObject(provObj); bMatchFound = true; } @@ -698,7 +617,7 @@ private void ProcessListProvider() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("ProviderMetadataUnavailable"), provName, exc.Message); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "ProviderMetadataUnavailable", ErrorCategory.NotSpecified, null)); continue; } @@ -708,8 +627,8 @@ private void ProcessListProvider() if (!bMatchFound) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("NoMatchingProvidersFound"), - _computerName, provPattern); - Exception exc = new Exception(msg); + ComputerName, provPattern); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "NoMatchingProvidersFound", ErrorCategory.ObjectNotFound, null)); } } @@ -728,7 +647,7 @@ private void ProcessFilterXml() // // Do minimal parsing of xmlQuery to determine if any direct channels or ETL files are in it. // - XmlElement root = _xmlQuery.DocumentElement; + XmlElement root = FilterXml.DocumentElement; XmlNodeList queryNodes = root.SelectNodes("//Query//Select"); foreach (XmlNode queryNode in queryNodes) { @@ -750,7 +669,7 @@ private void ProcessFilterXml() } } - EventLogQuery logQuery = new EventLogQuery(null, PathType.LogName, _xmlQuery.InnerXml); + EventLogQuery logQuery = new(null, PathType.LogName, FilterXml.InnerXml); logQuery.Session = eventLogSession; logQuery.ReverseDirection = !_oldest; @@ -769,13 +688,13 @@ private void ProcessFile() // At this point, _path array contains paths that might have wildcards, // environment variables or PS drives. Let's resolve those. // - for (int i = 0; i < _path.Length; i++) + for (int i = 0; i < Path.Length; i++) { - StringCollection resolvedPaths = ValidateAndResolveFilePath(_path[i]); + StringCollection resolvedPaths = ValidateAndResolveFilePath(Path[i]); 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}")); } } @@ -792,7 +711,7 @@ private void ProcessFile() } else { - logQuery = new EventLogQuery(_resolvedPaths[0], PathType.FilePath, _filter); + logQuery = new EventLogQuery(_resolvedPaths[0], PathType.FilePath, FilterXPath); } logQuery.Session = eventLogSession; @@ -817,7 +736,7 @@ private void ProcessHashQuery() return; } - EventLogQuery logQuery = new EventLogQuery(null, PathType.FilePath, query); + EventLogQuery logQuery = new(null, PathType.FilePath, query); logQuery.Session = eventLogSession; logQuery.TolerateQueryErrors = true; logQuery.ReverseDirection = !_oldest; @@ -834,30 +753,30 @@ private EventLogSession CreateSession() { EventLogSession eventLogSession = null; - if (_computerName == string.Empty) + if (ComputerName == string.Empty) { // Set _computerName to "localhost" for future error messages, // but do not use it for the connection to avoid RPC overhead. - _computerName = "localhost"; + ComputerName = "localhost"; - if (_credential == PSCredential.Empty) + if (Credential == PSCredential.Empty) { return new EventLogSession(); } } - else if (_credential == PSCredential.Empty) + else if (Credential == PSCredential.Empty) { - return new EventLogSession(_computerName); + return new EventLogSession(ComputerName); } // If we are here, either both computer name and credential were passed initially, // or credential only - we will use it with "localhost" - NetworkCredential netCred = (NetworkCredential)_credential; - eventLogSession = new EventLogSession(_computerName, + NetworkCredential netCred = (NetworkCredential)Credential; + eventLogSession = new EventLogSession(ComputerName, netCred.Domain, netCred.UserName, - _credential.Password, + Credential.Password, SessionAuthentication.Default ); // @@ -873,9 +792,9 @@ private EventLogSession CreateSession() // private void ReadEvents(EventLogQuery logQuery) { - using (EventLogReader readerObj = new EventLogReader(logQuery)) + using (EventLogReader readerObj = new(logQuery)) { - Int64 numEvents = 0; + long numEvents = 0; EventRecord evtObj = null; while (true) @@ -895,12 +814,12 @@ private void ReadEvents(EventLogQuery logQuery) break; } - if (_maxEvents != -1 && numEvents >= _maxEvents) + if (MaxEvents != -1 && numEvents >= MaxEvents) { break; } - PSObject outputObj = new PSObject(evtObj); + PSObject outputObj = new(evtObj); string evtMessage = _resourceMgr.GetString("NoEventMessage"); try @@ -924,7 +843,7 @@ private void ReadEvents(EventLogQuery logQuery) if (numEvents == 0) { string msg = _resourceMgr.GetString("NoMatchingEventsFound"); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "NoMatchingEventsFound", ErrorCategory.ObjectNotFound, null)); } } @@ -935,7 +854,7 @@ private void ReadEvents(EventLogQuery logQuery) // private string BuildStructuredQuery(EventLogSession eventLogSession) { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); switch (ParameterSetName) { @@ -967,7 +886,7 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) if (_logNamesMatchingWildcard.Count > WindowsEventLogAPILimit) { string msg = _resourceMgr.GetString("LogCountLimitExceeded"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, _logNamesMatchingWildcard.Count, WindowsEventLogAPILimit)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, _logNamesMatchingWildcard.Count, WindowsEventLogAPILimit)); ThrowTerminatingError(new ErrorRecord(exc, "LogCountLimitExceeded", ErrorCategory.LimitsExceeded, null)); } @@ -975,7 +894,7 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) uint queryId = 0; foreach (string log in _logNamesMatchingWildcard) { - result.AppendFormat(CultureInfo.InvariantCulture, queryTemplate, new object[] { queryId++, log, _filter }); + result.AppendFormat(CultureInfo.InvariantCulture, queryTemplate, new object[] { queryId++, log, FilterXPath }); } result.Append(queryListClose); @@ -990,7 +909,7 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) foreach (string filePath in _resolvedPaths) { string properFilePath = filePrefix + filePath; - result.AppendFormat(CultureInfo.InvariantCulture, queryTemplate, new object[] { queryId++, properFilePath, _filter }); + result.AppendFormat(CultureInfo.InvariantCulture, queryTemplate, new object[] { queryId++, properFilePath, FilterXPath }); } result.Append(queryListClose); @@ -1003,7 +922,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; } @@ -1017,7 +936,7 @@ private string BuildStructuredQuery(EventLogSession eventLogSession) // private string BuildXPathFromHashTable(Hashtable hash) { - StringBuilder xpathString = new StringBuilder(string.Empty); + StringBuilder xpathString = new(string.Empty); bool bDateTimeHandled = false; foreach (string key in hash.Keys) @@ -1106,13 +1025,13 @@ private string BuildXPathFromHashTable(Hashtable hash) // private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession) { - StringBuilder result = new StringBuilder(string.Empty); + StringBuilder result = new(string.Empty); result.Append(queryListOpen); uint queryId = 0; - foreach (Hashtable hash in _selector) + foreach (Hashtable hash in FilterHashtable) { string xpathString = string.Empty; string xpathStringSuppress = string.Empty; @@ -1123,12 +1042,12 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession // Local queriedLogsQueryMap will hold names of logs or files to be queried // mapped to the actual query strings being built up. // - Dictionary queriedLogsQueryMap = new Dictionary(); + Dictionary queriedLogsQueryMap = new(); // // queriedLogsQueryMapSuppress is the same as queriedLogsQueryMap but for // - Dictionary queriedLogsQueryMapSuppress = new Dictionary(); + Dictionary queriedLogsQueryMapSuppress = new(); // // Process log, _path, or provider parameters first @@ -1139,7 +1058,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession // if (hash.ContainsKey(hashkey_logname_lc)) { - List logPatterns = new List(); + List logPatterns = new(); if (hash[hashkey_logname_lc] is Array) { foreach (object elt in (Array)hash[hashkey_logname_lc]) @@ -1194,7 +1113,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession if (hash.ContainsKey(hashkey_providername_lc)) { - List provPatterns = new List(); + List provPatterns = new(); if (hash[hashkey_providername_lc] is Array) { foreach (object elt in (Array)hash[hashkey_providername_lc]) @@ -1227,7 +1146,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession } else { - List keysList = new List(queriedLogsQueryMap.Keys); + List keysList = new(queriedLogsQueryMap.Keys); bool bRemovedIrrelevantLogs = false; foreach (string queriedLog in keysList) { @@ -1257,7 +1176,7 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession if (bRemovedIrrelevantLogs && (queriedLogsQueryMap.Count == 0)) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("LogsAndProvidersDontOverlap")); - Exception exc = new Exception(msg); + Exception exc = new(msg); WriteError(new ErrorRecord(exc, "LogsAndProvidersDontOverlap", ErrorCategory.InvalidArgument, null)); continue; } @@ -1282,8 +1201,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); } @@ -1347,27 +1265,26 @@ private string BuildStructuredQueryFromHashTable(EventLogSession eventLogSession // HandleEventIdHashValue helper for hashtable structured query builder. // Constructs and returns EventId XPath portion as a string. // - private string HandleEventIdHashValue(object value) + private static string HandleEventIdHashValue(object value) { - StringBuilder ret = new StringBuilder(); - Array idsArray = value as Array; - if (idsArray != null) + StringBuilder ret = new(); + if (value is Array idsArray) { - ret.Append("("); + ret.Append('('); for (int i = 0; i < idsArray.Length; i++) { - ret.Append(SystemEventIDTemplate).Append(idsArray.GetValue(i).ToString()).Append(")"); + ret.Append(SystemEventIDTemplate).Append(idsArray.GetValue(i).ToString()).Append(')'); if (i < (idsArray.Length - 1)) { ret.Append(" or "); } } - ret.Append(")"); + ret.Append(')'); } else { - ret.Append(SystemEventIDTemplate).Append(value).Append(")"); + ret.Append(SystemEventIDTemplate).Append(value).Append(')'); } return ret.ToString(); @@ -1377,27 +1294,26 @@ private string HandleEventIdHashValue(object value) // HandleLevelHashValue helper for hashtable structured query builder. // Constructs and returns Level XPath portion as a string. // - private string HandleLevelHashValue(object value) + private static string HandleLevelHashValue(object value) { - StringBuilder ret = new StringBuilder(); - Array levelsArray = value as Array; - if (levelsArray != null) + StringBuilder ret = new(); + if (value is Array levelsArray) { - ret.Append("("); + ret.Append('('); for (int i = 0; i < levelsArray.Length; i++) { - ret.Append(SystemLevelTemplate).Append(levelsArray.GetValue(i).ToString()).Append(")"); + ret.Append(SystemLevelTemplate).Append(levelsArray.GetValue(i).ToString()).Append(')'); if (i < (levelsArray.Length - 1)) { ret.Append(" or "); } } - ret.Append(")"); + ret.Append(')'); } else { - ret.Append(SystemLevelTemplate).Append(value).Append(")"); + ret.Append(SystemLevelTemplate).Append(value).Append(')'); } return ret.ToString(); @@ -1409,11 +1325,10 @@ private 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) { @@ -1458,13 +1373,13 @@ private string HandleContextHashValue(object value) { try { - NTAccount acct = new NTAccount(value.ToString()); + NTAccount acct = new(value.ToString()); sidCandidate = (SecurityIdentifier)acct.Translate(typeof(SecurityIdentifier)); } catch (ArgumentException exc) { string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("InvalidContext"), value.ToString()); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "InvalidContext", ErrorCategory.InvalidArgument, null)); return string.Empty; } @@ -1480,8 +1395,8 @@ private string HandleContextHashValue(object value) // private string HandleStartTimeHashValue(object value, Hashtable hash) { - StringBuilder ret = new StringBuilder(); - DateTime startTime = new DateTime(); + StringBuilder ret = new(); + DateTime startTime = new(); if (!StringToDateTime(value.ToString(), ref startTime)) { return string.Empty; @@ -1492,7 +1407,7 @@ private string HandleStartTimeHashValue(object value, Hashtable hash) if (hash.ContainsKey(hashkey_endtime_lc)) { - DateTime endTime = new DateTime(); + DateTime endTime = new(); if (!StringToDateTime(hash[hashkey_endtime_lc].ToString(), ref endTime)) { return string.Empty; @@ -1523,8 +1438,8 @@ private string HandleStartTimeHashValue(object value, Hashtable hash) // private string HandleEndTimeHashValue(object value, Hashtable hash) { - StringBuilder ret = new StringBuilder(); - DateTime endTime = new DateTime(); + StringBuilder ret = new(); + DateTime endTime = new(); if (!StringToDateTime(value.ToString(), ref endTime)) { return string.Empty; @@ -1536,7 +1451,7 @@ private string HandleEndTimeHashValue(object value, Hashtable hash) if (hash.ContainsKey(hashkey_starttime_lc)) { - DateTime startTime = new DateTime(); + DateTime startTime = new(); if (!StringToDateTime(hash[hashkey_starttime_lc].ToString(), ref startTime)) { return string.Empty; @@ -1565,13 +1480,12 @@ private string HandleEndTimeHashValue(object value, Hashtable hash) // HandleDataHashValue helper for hashtable structured query builder. // Constructs and returns EventData/Data XPath portion as a string. // - private string HandleDataHashValue(object value) + private static string HandleDataHashValue(object value) { - StringBuilder ret = new StringBuilder(); - Array dataArray = value as Array; - if (dataArray != null) + StringBuilder ret = new(); + if (value is Array dataArray) { - ret.Append("("); + ret.Append('('); for (int i = 0; i < dataArray.Length; i++) { ret.AppendFormat(CultureInfo.InvariantCulture, DataTemplate, dataArray.GetValue(i).ToString()); @@ -1581,7 +1495,7 @@ private string HandleDataHashValue(object value) } } - ret.Append(")"); + ret.Append(')'); } else { @@ -1596,13 +1510,12 @@ private string HandleDataHashValue(object value) // Constructs and returns named event data field XPath portion as a string. // Fix Issue #2327 // - private string HandleNamedDataHashValue(string key, object value) + private static string HandleNamedDataHashValue(string key, object value) { - StringBuilder ret = new StringBuilder(); - Array dataArray = value as Array; - if (dataArray != null) + StringBuilder ret = new(); + if (value is Array dataArray) { - ret.Append("("); + ret.Append('('); for (int i = 0; i < dataArray.Length; i++) { ret.AppendFormat(CultureInfo.InvariantCulture, @@ -1614,7 +1527,7 @@ private string HandleNamedDataHashValue(string key, object value) } } - ret.Append(")"); + ret.Append(')'); } else { @@ -1639,7 +1552,7 @@ private void CheckHashTableForQueryPathPresence(Hashtable hash) if (!isLogHash && !isProviderHash && !isPathHash) { string msg = _resourceMgr.GetString("LogProviderOrPathNeeded"); - Exception exc = new Exception(msg); + Exception exc = new(msg); ThrowTerminatingError(new ErrorRecord(exc, "LogProviderOrPathNeeded", ErrorCategory.InvalidArgument, null)); } } @@ -1655,7 +1568,7 @@ private void TerminateForNonEvtxFileWithoutOldest(string fileName) System.IO.Path.GetExtension(fileName).Equals(".evt", StringComparison.OrdinalIgnoreCase)) { string msg = _resourceMgr.GetString("SpecifyOldestForEtlEvt"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, fileName)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, fileName)); ThrowTerminatingError(new ErrorRecord(exc, "SpecifyOldestForEtlEvt", ErrorCategory.InvalidArgument, fileName)); } } @@ -1675,7 +1588,7 @@ private bool ValidateLogName(string logName, EventLogSession eventLogSession) catch (EventLogNotFoundException) { string msg = _resourceMgr.GetString("NoMatchingLogsFound"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, _computerName, logName)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, ComputerName, logName)); WriteError(new ErrorRecord(exc, "NoMatchingLogsFound", ErrorCategory.ObjectNotFound, logName)); return false; } @@ -1684,7 +1597,7 @@ private bool ValidateLogName(string logName, EventLogSession eventLogSession) string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("LogInfoUnavailable"), logName, exc.Message); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "LogInfoUnavailable", ErrorCategory.NotSpecified, null)); return false; } @@ -1694,7 +1607,7 @@ private bool ValidateLogName(string logName, EventLogSession eventLogSession) if (logObj.LogType == EventLogType.Debug || logObj.LogType == EventLogType.Analytical) { string msg = _resourceMgr.GetString("SpecifyOldestForLog"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, logName)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, logName)); ThrowTerminatingError(new ErrorRecord(exc, "SpecifyOldestForLog", ErrorCategory.InvalidArgument, logName)); } } @@ -1707,7 +1620,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 { @@ -1716,7 +1629,7 @@ private bool KeywordStringToInt64(string keyString, ref Int64 keyLong) catch (Exception exc) { string msg = _resourceMgr.GetString("KeywordLongExpected"); - Exception outerExc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, keyString), exc); + Exception outerExc = new(string.Format(CultureInfo.InvariantCulture, msg, keyString), exc); WriteError(new ErrorRecord(outerExc, "KeywordLongExpected", ErrorCategory.InvalidArgument, null)); return false; } @@ -1738,7 +1651,7 @@ private bool StringToDateTime(string dtString, ref DateTime dt) catch (FormatException exc) { string msg = _resourceMgr.GetString("DateTimeExpected"); - Exception outerExc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, dtString), exc); + Exception outerExc = new(string.Format(CultureInfo.InvariantCulture, msg, dtString), exc); WriteError(new ErrorRecord(outerExc, "DateTimeExpected", ErrorCategory.InvalidArgument, null)); return false; } @@ -1754,7 +1667,7 @@ private bool StringToDateTime(string dtString, ref DateTime dt) // private StringCollection ValidateAndResolveFilePath(string path) { - StringCollection retColl = new StringCollection(); + StringCollection retColl = new(); Collection resolvedPathSubset = null; try @@ -1795,7 +1708,7 @@ private StringCollection ValidateAndResolveFilePath(string path) if (pi.Provider.Name != "FileSystem") { string msg = _resourceMgr.GetString("NotAFileSystemPath"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, path)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, path)); WriteError(new ErrorRecord(exc, "NotAFileSystemPath", ErrorCategory.InvalidArgument, path)); continue; } @@ -1812,7 +1725,7 @@ private StringCollection ValidateAndResolveFilePath(string path) if (!WildcardPattern.ContainsWildcardCharacters(path)) { string msg = _resourceMgr.GetString("NotALogFile"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, pi.ProviderPath)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, pi.ProviderPath)); WriteError(new ErrorRecord(exc, "NotALogFile", ErrorCategory.InvalidArgument, path)); } @@ -1834,7 +1747,7 @@ private StringCollection ValidateAndResolveFilePath(string path) // private void CheckHashTablesForNullValues() { - foreach (Hashtable hash in _selector) + foreach (Hashtable hash in FilterHashtable) { foreach (string key in hash.Keys) { @@ -1842,20 +1755,19 @@ private void CheckHashTablesForNullValues() if (value == null) { string msg = _resourceMgr.GetString("NullNotAllowedInHashtable"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, key)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, key)); ThrowTerminatingError(new ErrorRecord(exc, "NullNotAllowedInHashtable", ErrorCategory.InvalidArgument, key)); } else { - Array eltArray = value as Array; - if (eltArray != null) + if (value is Array eltArray) { foreach (object elt in eltArray) { if (elt == null) { string msg = _resourceMgr.GetString("NullNotAllowedInHashtable"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, key)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, key)); ThrowTerminatingError(new ErrorRecord(exc, "NullNotAllowedInHashtable", ErrorCategory.InvalidArgument, key)); } } @@ -1876,13 +1788,13 @@ private string AddProviderPredicatesToFilter(StringCollection providers) { if (providers.Count == 0) { - return _filter; + return FilterXPath; } - string ret = _filter; + string ret = FilterXPath; string predicate = BuildProvidersPredicate(providers); - if (_filter.Equals("*", StringComparison.OrdinalIgnoreCase)) + if (FilterXPath.Equals("*", StringComparison.OrdinalIgnoreCase)) { ret += "[" + predicate + "]"; } @@ -1891,7 +1803,7 @@ private string AddProviderPredicatesToFilter(StringCollection providers) // // Extend the XPath provided in the _filter // - int lastPredClose = _filter.LastIndexOf(']'); + int lastPredClose = FilterXPath.LastIndexOf(']'); if (lastPredClose == -1) { ret += "[" + predicate + "]"; @@ -1910,24 +1822,24 @@ private string AddProviderPredicatesToFilter(StringCollection providers) // "System/Provider[@Name='a' or @Name='b']" // for all provider names specified in the "providers" argument. // - private string BuildProvidersPredicate(StringCollection providers) + private static string BuildProvidersPredicate(StringCollection providers) { if (providers.Count == 0) { return string.Empty; } - StringBuilder predicate = new StringBuilder("System/Provider["); + StringBuilder predicate = new("System/Provider["); for (int i = 0; i < providers.Count; i++) { - predicate.Append("@Name='").Append(providers[i]).Append("'"); + predicate.Append("@Name='").Append(providers[i]).Append('\''); if (i < (providers.Count - 1)) { predicate.Append(" or "); } } - predicate.Append("]"); + predicate.Append(']'); return predicate.ToString(); } @@ -1946,9 +1858,9 @@ private string BuildAllProvidersPredicate() return string.Empty; } - StringBuilder predicate = new StringBuilder("System/Provider["); + StringBuilder predicate = new("System/Provider["); - List uniqueProviderNames = new List(); + List uniqueProviderNames = new(); foreach (string logKey in _providersByLogMap.Keys) { @@ -1964,14 +1876,14 @@ private string BuildAllProvidersPredicate() for (int i = 0; i < uniqueProviderNames.Count; i++) { - predicate.Append("@Name='").Append(uniqueProviderNames[i]).Append("'"); + predicate.Append("@Name='").Append(uniqueProviderNames[i]).Append('\''); if (i < uniqueProviderNames.Count - 1) { predicate.Append(" or "); } } - predicate.Append("]"); + predicate.Append(']'); return predicate.ToString(); } @@ -1987,7 +1899,7 @@ private void AddLogsForProviderToInternalMap(EventLogSession eventLogSession, st { try { - ProviderMetadata providerMetadata = new ProviderMetadata(providerName, eventLogSession, CultureInfo.CurrentCulture); + ProviderMetadata providerMetadata = new(providerName, eventLogSession, CultureInfo.CurrentCulture); System.Collections.IEnumerable logLinks = providerMetadata.LogLinks; @@ -1999,7 +1911,7 @@ private void AddLogsForProviderToInternalMap(EventLogSession eventLogSession, st // Skip direct ETW channels unless -force is present. // Error out for direct channels unless -oldest is present. // - EventLogConfiguration logObj = new EventLogConfiguration(logLink.LogName, eventLogSession); + EventLogConfiguration logObj = new(logLink.LogName, eventLogSession); if (logObj.LogType == EventLogType.Debug || logObj.LogType == EventLogType.Analytical) { if (!Force.IsPresent) @@ -2012,7 +1924,7 @@ private void AddLogsForProviderToInternalMap(EventLogSession eventLogSession, st WriteVerbose(string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("ProviderLogLink"), providerName, logLink.LogName)); - StringCollection provColl = new StringCollection(); + StringCollection provColl = new(); provColl.Add(providerName.ToLowerInvariant()); _providersByLogMap.Add(logLink.LogName.ToLowerInvariant(), provColl); @@ -2038,7 +1950,7 @@ private void AddLogsForProviderToInternalMap(EventLogSession eventLogSession, st string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("ProviderMetadataUnavailable"), providerName, exc.Message); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "ProviderMetadataUnavailable", ErrorCategory.NotSpecified, null)); return; } @@ -2064,7 +1976,7 @@ private void FindLogNamesMatchingWildcards(EventLogSession eventLogSession, IEnu foreach (string logPattern in logPatterns) { bool bMatched = false; - WildcardPattern wildLogPattern = new WildcardPattern(logPattern, WildcardOptions.IgnoreCase); + WildcardPattern wildLogPattern = new(logPattern, WildcardOptions.IgnoreCase); foreach (string actualLogName in eventLogSession.GetLogNames()) { @@ -2087,7 +1999,7 @@ private void FindLogNamesMatchingWildcards(EventLogSession eventLogSession, IEnu string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("LogInfoUnavailable"), actualLogName, exc.Message); - Exception outerExc = new Exception(msg, exc); + Exception outerExc = new(msg, exc); WriteError(new ErrorRecord(outerExc, "LogInfoUnavailable", ErrorCategory.NotSpecified, null)); continue; } @@ -2114,7 +2026,7 @@ private void FindLogNamesMatchingWildcards(EventLogSession eventLogSession, IEnu if (!bMatched) { string msg = _resourceMgr.GetString("NoMatchingLogsFound"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, _computerName, logPattern)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, ComputerName, logPattern)); WriteError(new ErrorRecord(exc, "NoMatchingLogsFound", ErrorCategory.ObjectNotFound, logPattern)); } } @@ -2133,7 +2045,7 @@ private void FindProvidersByLogForWildcardPatterns(EventLogSession eventLogSessi foreach (string provPattern in providerPatterns) { bool bMatched = false; - WildcardPattern wildProvPattern = new WildcardPattern(provPattern, WildcardOptions.IgnoreCase); + WildcardPattern wildProvPattern = new(provPattern, WildcardOptions.IgnoreCase); foreach (string provName in eventLogSession.GetProviderNames()) { @@ -2142,7 +2054,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; } @@ -2151,11 +2063,10 @@ private void FindProvidersByLogForWildcardPatterns(EventLogSession eventLogSessi if (!bMatched) { string msg = _resourceMgr.GetString("NoMatchingProvidersFound"); - Exception exc = new Exception(string.Format(CultureInfo.InvariantCulture, msg, _computerName, provPattern)); + Exception exc = new(string.Format(CultureInfo.InvariantCulture, msg, ComputerName, provPattern)); WriteError(new ErrorRecord(exc, "NoMatchingProvidersFound", ErrorCategory.ObjectNotFound, provPattern)); } } } } } - 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..729334d0605 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; } @@ -679,4 +679,3 @@ private void WriteSampleSetObject(PerformanceCounterSampleSet set, bool firstSet } } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index e3dc8b6256e..9552f72c83a 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -2,12 +2,13 @@ PowerShell's Microsoft.PowerShell.Commands.Diagnostics project - $(NoWarn);CS1591 + $(NoWarn);CS1591;CA1416 Microsoft.PowerShell.Commands.Diagnostics + diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs index 216e9bca7ea..cdddba939f3 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs @@ -9,16 +9,15 @@ using System.Globalization; using System.IO; using System.Management.Automation; -using System.Reflection; using System.Resources; using System.Xml; namespace Microsoft.PowerShell.Commands { - /// + /// /// Class that implements the New-WinEvent cmdlet. /// This cmdlet writes a new Etw event using the provider specified in parameter. - /// + /// [Cmdlet(VerbsCommon.New, "WinEvent", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096808")] public sealed class NewWinEventCommand : PSCmdlet { @@ -28,7 +27,7 @@ public sealed class NewWinEventCommand : PSCmdlet private const string TemplateTag = "template"; private const string DataTag = "data"; - private ResourceManager _resourceMgr = Microsoft.PowerShell.Commands.Diagnostics.Common.CommonUtilities.GetResourceManager(); + private readonly ResourceManager _resourceMgr = Microsoft.PowerShell.Commands.Diagnostics.Common.CommonUtilities.GetResourceManager(); /// /// ProviderName. @@ -37,20 +36,7 @@ public sealed class NewWinEventCommand : PSCmdlet Position = 0, Mandatory = true, ParameterSetName = ParameterAttribute.AllParameterSets)] - public string ProviderName - { - get - { - return _providerName; - } - - set - { - _providerName = value; - } - } - - private string _providerName; + public string ProviderName { get; set; } /// /// Id (EventId defined in manifest file) @@ -110,20 +96,7 @@ public byte Version SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Target = "Microsoft.PowerShell.Commands", Justification = "A string[] is required here because that is the type Powershell supports")] - public object[] Payload - { - get - { - return _payload; - } - - set - { - _payload = value; - } - } - - private object[] _payload; + public object[] Payload { get; set; } /// /// BeginProcessing. @@ -138,16 +111,16 @@ protected override void BeginProcessing() private void LoadProvider() { - if (string.IsNullOrEmpty(_providerName)) + if (string.IsNullOrEmpty(ProviderName)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("ProviderNotSpecified")), "ProviderName"); } - using (EventLogSession session = new EventLogSession()) + using (EventLogSession session = new()) { foreach (string providerName in session.GetProviderNames()) { - if (string.Equals(providerName, _providerName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(providerName, ProviderName, StringComparison.OrdinalIgnoreCase)) { try { @@ -166,7 +139,7 @@ private void LoadProvider() if (_providerMetadata == null) { - string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("NoProviderFound"), _providerName); + string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("NoProviderFound"), ProviderName); throw new ArgumentException(msg); } } @@ -175,7 +148,7 @@ private void LoadEventDescriptor() { if (_idSpecified) { - List matchedEvents = new List(); + List matchedEvents = new(); foreach (EventMetadata emd in _providerMetadata.Events) { if (emd.Id == _id) @@ -189,7 +162,7 @@ private void LoadEventDescriptor() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("IncorrectEventId"), _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -217,7 +190,7 @@ private void LoadEventDescriptor() _resourceMgr.GetString("IncorrectEventVersion"), _version, _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -227,7 +200,7 @@ private void LoadEventDescriptor() string msg = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("VersionNotSpecified"), _id, - _providerName); + ProviderName); throw new EventWriteException(msg); } @@ -246,7 +219,7 @@ private bool VerifyTemplate(EventMetadata emd) { if (emd.Template != null) { - XmlReaderSettings readerSettings = new XmlReaderSettings + XmlReaderSettings readerSettings = new() { CheckCharacters = false, IgnoreComments = true, @@ -270,8 +243,8 @@ private bool VerifyTemplate(EventMetadata emd) } } - if ((_payload == null && definedParameterCount != 0) - || ((_payload != null) && _payload.Length != definedParameterCount)) + if ((Payload == null && definedParameterCount != 0) + || ((Payload != null) && Payload.Length != definedParameterCount)) { string warning = string.Format(CultureInfo.InvariantCulture, _resourceMgr.GetString("PayloadMismatch"), _id, emd.Template); WriteWarning(warning); @@ -314,25 +287,25 @@ private static EventDescriptor CreateEventDescriptor(ProviderMetadata providerMe /// protected override void ProcessRecord() { - using (EventProvider provider = new EventProvider(_providerMetadata.Id)) + using (EventProvider provider = new(_providerMetadata.Id)) { EventDescriptor ed = _eventDescriptor.Value; - if (_payload != null && _payload.Length > 0) + if (Payload != null && Payload.Length > 0) { - for (int i = 0; i < _payload.Length; i++) + for (int i = 0; i < Payload.Length; i++) { - if (_payload[i] == null) + if (Payload[i] == null) { - _payload[i] = string.Empty; + Payload[i] = string.Empty; } } - provider.WriteEvent(ref ed, _payload); + provider.WriteEvent(in ed, Payload); } else { - provider.WriteEvent(ref ed); + provider.WriteEvent(in ed); } } @@ -344,14 +317,13 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - if (_providerMetadata != null) - _providerMetadata.Dispose(); + _providerMetadata?.Dispose(); base.EndProcessing(); } } - internal class EventWriteException : Exception + internal sealed class EventWriteException : Exception { internal EventWriteException(string msg, Exception innerException) : base(msg, innerException) diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs index e535535d25e..6f5d8f6e5ec 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs @@ -2,13 +2,11 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using Microsoft.PowerShell.Commands.GetCounter; using Microsoft.Win32; @@ -17,92 +15,92 @@ namespace Microsoft.Powershell.Commands.GetCounter.PdhNative { internal static class PdhResults { - public const long PDH_CSTATUS_VALID_DATA = 0x0L; - public const long PDH_CSTATUS_NEW_DATA = 0x1L; - public const long PDH_CSTATUS_NO_MACHINE = 0x800007D0L; - public const long PDH_CSTATUS_NO_INSTANCE = 0x800007D1L; - public const long PDH_MORE_DATA = 0x800007D2L; - public const long PDH_CSTATUS_ITEM_NOT_VALIDATED = 0x800007D3L; - public const long PDH_RETRY = 0x800007D4L; - public const long PDH_NO_DATA = 0x800007D5L; - public const long PDH_CALC_NEGATIVE_DENOMINATOR = 0x800007D6L; - public const long PDH_CALC_NEGATIVE_TIMEBASE = 0x800007D7L; - public const long PDH_CALC_NEGATIVE_VALUE = 0x800007D8L; - public const long PDH_DIALOG_CANCELLED = 0x800007D9L; - public const long PDH_END_OF_LOG_FILE = 0x800007DAL; - public const long PDH_ASYNC_QUERY_TIMEOUT = 0x800007DBL; - public const long PDH_CANNOT_SET_DEFAULT_REALTIME_DATASOURCE = 0x800007DCL; - public const long PDH_UNABLE_MAP_NAME_FILES = 0x80000BD5L; - public const long PDH_PLA_VALIDATION_WARNING = 0x80000BF3L; - public const long PDH_CSTATUS_NO_OBJECT = 0xC0000BB8L; - public const long PDH_CSTATUS_NO_COUNTER = 0xC0000BB9L; - public const long PDH_CSTATUS_INVALID_DATA = 0xC0000BBAL; - public const long PDH_MEMORY_ALLOCATION_FAILURE = 0xC0000BBBL; - public const long PDH_INVALID_HANDLE = 0xC0000BBCL; - public const long PDH_INVALID_ARGUMENT = 0xC0000BBDL; - public const long PDH_FUNCTION_NOT_FOUND = 0xC0000BBEL; - public const long PDH_CSTATUS_NO_COUNTERNAME = 0xC0000BBFL; - public const long PDH_CSTATUS_BAD_COUNTERNAME = 0xC0000BC0L; - public const long PDH_INVALID_BUFFER = 0xC0000BC1L; - public const long PDH_INSUFFICIENT_BUFFER = 0xC0000BC2L; - public const long PDH_CANNOT_CONNECT_MACHINE = 0xC0000BC3L; - public const long PDH_INVALID_PATH = 0xC0000BC4L; - public const long PDH_INVALID_INSTANCE = 0xC0000BC5L; - public const long PDH_INVALID_DATA = 0xC0000BC6L; - public const long PDH_NO_DIALOG_DATA = 0xC0000BC7L; - public const long PDH_CANNOT_READ_NAME_STRINGS = 0xC0000BC8L; - public const long PDH_LOG_FILE_CREATE_ERROR = 0xC0000BC9L; - public const long PDH_LOG_FILE_OPEN_ERROR = 0xC0000BCAL; - public const long PDH_LOG_TYPE_NOT_FOUND = 0xC0000BCBL; - public const long PDH_NO_MORE_DATA = 0xC0000BCCL; - public const long PDH_ENTRY_NOT_IN_LOG_FILE = 0xC0000BCDL; - public const long PDH_DATA_SOURCE_IS_LOG_FILE = 0xC0000BCEL; - public const long PDH_DATA_SOURCE_IS_REAL_TIME = 0xC0000BCFL; - public const long PDH_UNABLE_READ_LOG_HEADER = 0xC0000BD0L; - public const long PDH_FILE_NOT_FOUND = 0xC0000BD1L; - public const long PDH_FILE_ALREADY_EXISTS = 0xC0000BD2L; - public const long PDH_NOT_IMPLEMENTED = 0xC0000BD3L; - public const long PDH_STRING_NOT_FOUND = 0xC0000BD4L; - public const long PDH_UNKNOWN_LOG_FORMAT = 0xC0000BD6L; - public const long PDH_UNKNOWN_LOGSVC_COMMAND = 0xC0000BD7L; - public const long PDH_LOGSVC_QUERY_NOT_FOUND = 0xC0000BD8L; - public const long PDH_LOGSVC_NOT_OPENED = 0xC0000BD9L; - public const long PDH_WBEM_ERROR = 0xC0000BDAL; - public const long PDH_ACCESS_DENIED = 0xC0000BDBL; - public const long PDH_LOG_FILE_TOO_SMALL = 0xC0000BDCL; - public const long PDH_INVALID_DATASOURCE = 0xC0000BDDL; - public const long PDH_INVALID_SQLDB = 0xC0000BDEL; - public const long PDH_NO_COUNTERS = 0xC0000BDFL; - public const long PDH_SQL_ALLOC_FAILED = 0xC0000BE0L; - public const long PDH_SQL_ALLOCCON_FAILED = 0xC0000BE1L; - public const long PDH_SQL_EXEC_DIRECT_FAILED = 0xC0000BE2L; - public const long PDH_SQL_FETCH_FAILED = 0xC0000BE3L; - public const long PDH_SQL_ROWCOUNT_FAILED = 0xC0000BE4L; - public const long PDH_SQL_MORE_RESULTS_FAILED = 0xC0000BE5L; - public const long PDH_SQL_CONNECT_FAILED = 0xC0000BE6L; - public const long PDH_SQL_BIND_FAILED = 0xC0000BE7L; - public const long PDH_CANNOT_CONNECT_WMI_SERVER = 0xC0000BE8L; - public const long PDH_PLA_COLLECTION_ALREADY_RUNNING = 0xC0000BE9L; - public const long PDH_PLA_ERROR_SCHEDULE_OVERLAP = 0xC0000BEAL; - public const long PDH_PLA_COLLECTION_NOT_FOUND = 0xC0000BEBL; - public const long PDH_PLA_ERROR_SCHEDULE_ELAPSED = 0xC0000BECL; - public const long PDH_PLA_ERROR_NOSTART = 0xC0000BEDL; - public const long PDH_PLA_ERROR_ALREADY_EXISTS = 0xC0000BEEL; - public const long PDH_PLA_ERROR_TYPE_MISMATCH = 0xC0000BEFL; - public const long PDH_PLA_ERROR_FILEPATH = 0xC0000BF0L; - public const long PDH_PLA_SERVICE_ERROR = 0xC0000BF1L; - public const long PDH_PLA_VALIDATION_ERROR = 0xC0000BF2L; - public const long PDH_PLA_ERROR_NAME_TOO_LONG = 0xC0000BF4L; - public const long PDH_INVALID_SQL_LOG_FORMAT = 0xC0000BF5L; - public const long PDH_COUNTER_ALREADY_IN_QUERY = 0xC0000BF6L; - public const long PDH_BINARY_LOG_CORRUPT = 0xC0000BF7L; - public const long PDH_LOG_SAMPLE_TOO_SMALL = 0xC0000BF8L; - public const long PDH_OS_LATER_VERSION = 0xC0000BF9L; - public const long PDH_OS_EARLIER_VERSION = 0xC0000BFAL; - public const long PDH_INCORRECT_APPEND_TIME = 0xC0000BFBL; - public const long PDH_UNMATCHED_APPEND_COUNTER = 0xC0000BFCL; - public const long PDH_SQL_ALTER_DETAIL_FAILED = 0xC0000BFDL; - public const long PDH_QUERY_PERF_DATA_TIMEOUT = 0xC0000BFEL; + public const uint PDH_CSTATUS_VALID_DATA = 0x0; + public const uint PDH_CSTATUS_NEW_DATA = 0x1; + public const uint PDH_CSTATUS_NO_MACHINE = 0x800007D0; + public const uint PDH_CSTATUS_NO_INSTANCE = 0x800007D1; + public const uint PDH_MORE_DATA = 0x800007D2; + public const uint PDH_CSTATUS_ITEM_NOT_VALIDATED = 0x800007D3; + public const uint PDH_RETRY = 0x800007D4; + public const uint PDH_NO_DATA = 0x800007D5; + public const uint PDH_CALC_NEGATIVE_DENOMINATOR = 0x800007D6; + public const uint PDH_CALC_NEGATIVE_TIMEBASE = 0x800007D7; + public const uint PDH_CALC_NEGATIVE_VALUE = 0x800007D8; + public const uint PDH_DIALOG_CANCELLED = 0x800007D9; + public const uint PDH_END_OF_LOG_FILE = 0x800007DA; + public const uint PDH_ASYNC_QUERY_TIMEOUT = 0x800007DB; + public const uint PDH_CANNOT_SET_DEFAULT_REALTIME_DATASOURCE = 0x800007DC; + public const uint PDH_UNABLE_MAP_NAME_FILES = 0x80000BD5; + public const uint PDH_PLA_VALIDATION_WARNING = 0x80000BF3; + public const uint PDH_CSTATUS_NO_OBJECT = 0xC0000BB8; + public const uint PDH_CSTATUS_NO_COUNTER = 0xC0000BB9; + public const uint PDH_CSTATUS_INVALID_DATA = 0xC0000BBA; + public const uint PDH_MEMORY_ALLOCATION_FAILURE = 0xC0000BBB; + public const uint PDH_INVALID_HANDLE = 0xC0000BBC; + public const uint PDH_INVALID_ARGUMENT = 0xC0000BBD; + public const uint PDH_FUNCTION_NOT_FOUND = 0xC0000BBE; + public const uint PDH_CSTATUS_NO_COUNTERNAME = 0xC0000BBF; + public const uint PDH_CSTATUS_BAD_COUNTERNAME = 0xC0000BC0; + public const uint PDH_INVALID_BUFFER = 0xC0000BC1; + public const uint PDH_INSUFFICIENT_BUFFER = 0xC0000BC2; + public const uint PDH_CANNOT_CONNECT_MACHINE = 0xC0000BC3; + public const uint PDH_INVALID_PATH = 0xC0000BC4; + public const uint PDH_INVALID_INSTANCE = 0xC0000BC5; + public const uint PDH_INVALID_DATA = 0xC0000BC6; + public const uint PDH_NO_DIALOG_DATA = 0xC0000BC7; + public const uint PDH_CANNOT_READ_NAME_STRINGS = 0xC0000BC8; + public const uint PDH_LOG_FILE_CREATE_ERROR = 0xC0000BC9; + public const uint PDH_LOG_FILE_OPEN_ERROR = 0xC0000BCA; + public const uint PDH_LOG_TYPE_NOT_FOUND = 0xC0000BCB; + public const uint PDH_NO_MORE_DATA = 0xC0000BCC; + public const uint PDH_ENTRY_NOT_IN_LOG_FILE = 0xC0000BCD; + public const uint PDH_DATA_SOURCE_IS_LOG_FILE = 0xC0000BCE; + public const uint PDH_DATA_SOURCE_IS_REAL_TIME = 0xC0000BCF; + public const uint PDH_UNABLE_READ_LOG_HEADER = 0xC0000BD0; + public const uint PDH_FILE_NOT_FOUND = 0xC0000BD1; + public const uint PDH_FILE_ALREADY_EXISTS = 0xC0000BD2; + public const uint PDH_NOT_IMPLEMENTED = 0xC0000BD3; + public const uint PDH_STRING_NOT_FOUND = 0xC0000BD4; + public const uint PDH_UNKNOWN_LOG_FORMAT = 0xC0000BD6; + public const uint PDH_UNKNOWN_LOGSVC_COMMAND = 0xC0000BD7; + public const uint PDH_LOGSVC_QUERY_NOT_FOUND = 0xC0000BD8; + public const uint PDH_LOGSVC_NOT_OPENED = 0xC0000BD9; + public const uint PDH_WBEM_ERROR = 0xC0000BDA; + public const uint PDH_ACCESS_DENIED = 0xC0000BDB; + public const uint PDH_LOG_FILE_TOO_SMALL = 0xC0000BDC; + public const uint PDH_INVALID_DATASOURCE = 0xC0000BDD; + public const uint PDH_INVALID_SQLDB = 0xC0000BDE; + public const uint PDH_NO_COUNTERS = 0xC0000BDF; + public const uint PDH_SQL_ALLOC_FAILED = 0xC0000BE0; + public const uint PDH_SQL_ALLOCCON_FAILED = 0xC0000BE1; + public const uint PDH_SQL_EXEC_DIRECT_FAILED = 0xC0000BE2; + public const uint PDH_SQL_FETCH_FAILED = 0xC0000BE3; + public const uint PDH_SQL_ROWCOUNT_FAILED = 0xC0000BE4; + public const uint PDH_SQL_MORE_RESULTS_FAILED = 0xC0000BE5; + public const uint PDH_SQL_CONNECT_FAILED = 0xC0000BE6; + public const uint PDH_SQL_BIND_FAILED = 0xC0000BE7; + public const uint PDH_CANNOT_CONNECT_WMI_SERVER = 0xC0000BE8; + public const uint PDH_PLA_COLLECTION_ALREADY_RUNNING = 0xC0000BE9; + public const uint PDH_PLA_ERROR_SCHEDULE_OVERLAP = 0xC0000BEA; + public const uint PDH_PLA_COLLECTION_NOT_FOUND = 0xC0000BEB; + public const uint PDH_PLA_ERROR_SCHEDULE_ELAPSED = 0xC0000BEC; + public const uint PDH_PLA_ERROR_NOSTART = 0xC0000BED; + public const uint PDH_PLA_ERROR_ALREADY_EXISTS = 0xC0000BEE; + public const uint PDH_PLA_ERROR_TYPE_MISMATCH = 0xC0000BEF; + public const uint PDH_PLA_ERROR_FILEPATH = 0xC0000BF0; + public const uint PDH_PLA_SERVICE_ERROR = 0xC0000BF1; + public const uint PDH_PLA_VALIDATION_ERROR = 0xC0000BF2; + public const uint PDH_PLA_ERROR_NAME_TOO_LONG = 0xC0000BF4; + public const uint PDH_INVALID_SQL_LOG_FORMAT = 0xC0000BF5; + public const uint PDH_COUNTER_ALREADY_IN_QUERY = 0xC0000BF6; + public const uint PDH_BINARY_LOG_CORRUPT = 0xC0000BF7; + public const uint PDH_LOG_SAMPLE_TOO_SMALL = 0xC0000BF8; + public const uint PDH_OS_LATER_VERSION = 0xC0000BF9; + public const uint PDH_OS_EARLIER_VERSION = 0xC0000BFA; + public const uint PDH_INCORRECT_APPEND_TIME = 0xC0000BFB; + public const uint PDH_UNMATCHED_APPEND_COUNTER = 0xC0000BFC; + public const uint PDH_SQL_ALTER_DETAIL_FAILED = 0xC0000BFD; + public const uint PDH_QUERY_PERF_DATA_TIMEOUT = 0xC0000BFE; } internal static class PerfDetail @@ -192,7 +190,7 @@ internal struct CounterHandleNInstance public string InstanceName; } - internal class PdhHelper : IDisposable + internal sealed class PdhHelper : IDisposable { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct PDH_COUNTER_PATH_ELEMENTS @@ -227,7 +225,7 @@ private struct PDH_FMT_COUNTERVALUE_LARGE // [FieldOffset(4), MarshalAs(UnmanagedType.LPWStr)] // public string WideStringValue; - }; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_FMT_COUNTERVALUE_DOUBLE @@ -235,7 +233,7 @@ private struct PDH_FMT_COUNTERVALUE_DOUBLE public uint CStatus; public double doubleValue; - }; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_FMT_COUNTERVALUE_UNICODE @@ -244,7 +242,7 @@ private struct PDH_FMT_COUNTERVALUE_UNICODE [MarshalAs(UnmanagedType.LPWStr)] public string WideStringValue; - }; + } [StructLayout(LayoutKind.Sequential)] private struct PDH_RAW_COUNTER @@ -269,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)] @@ -375,7 +410,7 @@ private static extern uint PdhExpandWildCardPathH(PdhSafeDataSourceHandle hDataS private static extern uint PdhValidatePath(string szFullPathBuffer); [DllImport("pdh.dll", CharSet = CharSet.Unicode)] - private static extern uint PdhGetCounterInfo(IntPtr hCounter, [MarshalAs(UnmanagedType.U1)]bool bRetrieveExplainText, ref IntPtr pdwBufferSize, IntPtr lpBuffer); + private static extern uint PdhGetCounterInfo(IntPtr hCounter, [MarshalAs(UnmanagedType.U1)] bool bRetrieveExplainText, ref IntPtr pdwBufferSize, IntPtr lpBuffer); [DllImport("pdh.dll", CharSet = CharSet.Unicode)] private static extern uint PdhGetCounterTimeBase(IntPtr hCounter, out UInt64 pTimeBase); @@ -423,7 +458,7 @@ public void Dispose() // // m_ConsumerPathToHandleAndInstanceMap map is used for reading counter date (live or from files). // - private Dictionary _consumerPathToHandleAndInstanceMap = new Dictionary(); + private readonly Dictionary _consumerPathToHandleAndInstanceMap = new(); /// /// A helper reading in a Unicode string with embedded NULLs and splitting it into a StringCollection. @@ -431,8 +466,7 @@ public void Dispose() /// /// /// - - private void ReadPdhMultiString(ref IntPtr strNative, Int32 strSize, ref StringCollection strColl) + private static void ReadPdhMultiString(ref IntPtr strNative, Int32 strSize, ref StringCollection strColl) { Debug.Assert(strSize >= 2); int offset = 0; @@ -455,17 +489,16 @@ private void ReadPdhMultiString(ref IntPtr strNative, Int32 strSize, ref StringC strColl.AddRange(allSubstringsWithNulls.Split('\0')); } - private uint GetCounterInfoPlus(IntPtr hCounter, out UInt32 counterType, out UInt32 defaultScale, out UInt64 timeBase) + private static uint GetCounterInfoPlus(IntPtr hCounter, out UInt32 counterType, out UInt32 defaultScale, out UInt64 timeBase) { - uint res = 0; counterType = 0; defaultScale = 0; timeBase = 0; - Debug.Assert(hCounter != null); + Debug.Assert(hCounter != IntPtr.Zero); - IntPtr pBufferSize = new IntPtr(0); - res = PdhGetCounterInfo(hCounter, false, ref pBufferSize, IntPtr.Zero); + IntPtr pBufferSize = new(0); + uint res = PdhGetCounterInfo(hCounter, false, ref pBufferSize, IntPtr.Zero); if (res != PdhResults.PDH_MORE_DATA) { return res; @@ -477,11 +510,11 @@ private uint GetCounterInfoPlus(IntPtr hCounter, out UInt32 counterType, out UIn try { res = PdhGetCounterInfo(hCounter, false, ref pBufferSize, bufCounterInfo); - if (res == 0 && bufCounterInfo != IntPtr.Zero) + 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 @@ -490,7 +523,7 @@ private uint GetCounterInfoPlus(IntPtr hCounter, out UInt32 counterType, out UIn } res = PdhGetCounterTimeBase(hCounter, out timeBase); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -506,13 +539,13 @@ public uint ConnectToDataSource() } uint res = PdhHelper.PdhBindInputDataSource(out _hDataSource, null); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine("error in PdhBindInputDataSource: " + res); return res; } - return 0; + return PdhResults.PDH_CSTATUS_VALID_DATA; } /// /// Connects to a single named datasource, initializing m_hDataSource variable. @@ -527,7 +560,7 @@ public uint ConnectToDataSource(string dataSourceName) } uint res = PdhHelper.PdhBindInputDataSource(out _hDataSource, dataSourceName); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine("error in PdhBindInputDataSource: " + res); } @@ -557,7 +590,7 @@ public uint OpenQuery() { uint res = PdhOpenQueryH(_hDataSource, IntPtr.Zero, out _hQuery); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine("error in PdhOpenQueryH: " + res); } @@ -589,7 +622,7 @@ public uint SetQueryTimeRange(DateTime startTime, DateTime endTime) Debug.Assert(_hQuery != null); Debug.Assert(endTime >= startTime); - PDH_TIME_INFO pTimeInfo = new PDH_TIME_INFO(); + PDH_TIME_INFO pTimeInfo = new(); if (startTime != DateTime.MinValue && startTime.Kind == DateTimeKind.Local) { @@ -612,7 +645,7 @@ public uint SetQueryTimeRange(DateTime startTime, DateTime endTime) public uint EnumBlgFilesMachines(ref StringCollection machineNames) { - IntPtr MachineListTcharSizePtr = new IntPtr(0); + IntPtr MachineListTcharSizePtr = new(0); uint res = PdhHelper.PdhEnumMachinesH(_hDataSource, IntPtr.Zero, ref MachineListTcharSizePtr); if (res != PdhResults.PDH_MORE_DATA) { @@ -625,7 +658,7 @@ public uint EnumBlgFilesMachines(ref StringCollection machineNames) try { res = PdhHelper.PdhEnumMachinesH(_hDataSource, (IntPtr)strMachineList, ref MachineListTcharSizePtr); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { ReadPdhMultiString(ref strMachineList, MachineListTcharSizePtr.ToInt32(), ref machineNames); } @@ -640,7 +673,7 @@ public uint EnumBlgFilesMachines(ref StringCollection machineNames) public uint EnumObjects(string machineName, ref StringCollection objectNames) { - IntPtr pBufferSize = new IntPtr(0); + IntPtr pBufferSize = new(0); uint res = PdhEnumObjectsH(_hDataSource, machineName, IntPtr.Zero, ref pBufferSize, PerfDetail.PERF_DETAIL_WIZARD, false); if (res != PdhResults.PDH_MORE_DATA) { @@ -653,7 +686,7 @@ public uint EnumObjects(string machineName, ref StringCollection objectNames) try { res = PdhEnumObjectsH(_hDataSource, machineName, (IntPtr)strObjectList, ref pBufferSize, PerfDetail.PERF_DETAIL_WIZARD, false); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { ReadPdhMultiString(ref strObjectList, pBufferSize.ToInt32(), ref objectNames); } @@ -668,8 +701,8 @@ public uint EnumObjects(string machineName, ref StringCollection objectNames) public uint EnumObjectItems(string machineName, string objectName, ref StringCollection counterNames, ref StringCollection instanceNames) { - IntPtr pCounterBufferSize = new IntPtr(0); - IntPtr pInstanceBufferSize = new IntPtr(0); + IntPtr pCounterBufferSize = new(0); + IntPtr pInstanceBufferSize = new(0); uint res = PdhEnumObjectItemsH(_hDataSource, machineName, objectName, IntPtr.Zero, ref pCounterBufferSize, @@ -678,12 +711,12 @@ public uint EnumObjectItems(string machineName, string objectName, ref StringCol if (res == PdhResults.PDH_CSTATUS_NO_INSTANCE) { instanceNames.Clear(); - return 0; // masking the error + return PdhResults.PDH_CSTATUS_VALID_DATA; // masking the error } else if (res == PdhResults.PDH_CSTATUS_NO_OBJECT) { counterNames.Clear(); - return 0; // masking the error + return PdhResults.PDH_CSTATUS_VALID_DATA; // masking the error } else if (res != PdhResults.PDH_MORE_DATA) { @@ -716,7 +749,7 @@ public uint EnumObjectItems(string machineName, string objectName, ref StringCol strCountersList, ref pCounterBufferSize, strInstancesList, ref pInstanceBufferSize, PerfDetail.PERF_DETAIL_WIZARD, 0); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine("error in PdhEnumObjectItemsH 2nd call: " + res + "\n Counter buffer size is " // + pCounterBufferSize.ToInt32() + "\n Instance buffer size is " + pInstanceBufferSize.ToInt32()); @@ -750,18 +783,18 @@ public uint GetValidPathsFromFiles(ref StringCollection validPaths) { Debug.Assert(_hDataSource != null && !_hDataSource.IsInvalid, "Call ConnectToDataSource before GetValidPathsFromFiles"); - StringCollection machineNames = new StringCollection(); + StringCollection machineNames = new(); uint res = this.EnumBlgFilesMachines(ref machineNames); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } foreach (string machine in machineNames) { - StringCollection counterSets = new StringCollection(); + StringCollection counterSets = new(); res = this.EnumObjects(machine, ref counterSets); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -770,17 +803,17 @@ public uint GetValidPathsFromFiles(ref StringCollection validPaths) { // Console.WriteLine("Counter set " + counterSet); - StringCollection counterSetCounters = new StringCollection(); - StringCollection counterSetInstances = new StringCollection(); + StringCollection counterSetCounters = new(); + StringCollection counterSetInstances = new(); res = this.EnumObjectItems(machine, counterSet, ref counterSetCounters, ref counterSetInstances); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } res = this.GetValidPaths(machine, counterSet, ref counterSetCounters, ref counterSetInstances, ref validPaths); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -794,7 +827,7 @@ private bool IsPathValid(ref PDH_COUNTER_PATH_ELEMENTS pathElts, out string outP { bool ret = false; outPath = string.Empty; - IntPtr pPathBufferSize = new IntPtr(0); + IntPtr pPathBufferSize = new(0); uint res = PdhMakeCounterPath(ref pathElts, IntPtr.Zero, ref pPathBufferSize, 0); if (res != PdhResults.PDH_MORE_DATA) @@ -808,11 +841,11 @@ private bool IsPathValid(ref PDH_COUNTER_PATH_ELEMENTS pathElts, out string outP try { res = PdhMakeCounterPath(ref pathElts, strPath, ref pPathBufferSize, 0); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { outPath = Marshal.PtrToStringUni(strPath); - ret = (PdhValidatePathEx(_hDataSource, outPath) == 0); + ret = (PdhValidatePathEx(_hDataSource, outPath) == PdhResults.PDH_CSTATUS_VALID_DATA); } } finally @@ -825,13 +858,13 @@ private bool IsPathValid(ref PDH_COUNTER_PATH_ELEMENTS pathElts, out string outP public bool IsPathValid(string path) { - return (PdhValidatePathEx(_hDataSource, path) == 0); + return (PdhValidatePathEx(_hDataSource, path) == PdhResults.PDH_CSTATUS_VALID_DATA); } - private uint MakePath(PDH_COUNTER_PATH_ELEMENTS pathElts, out string outPath, bool bWildcardInstances) + private static uint MakePath(PDH_COUNTER_PATH_ELEMENTS pathElts, out string outPath, bool bWildcardInstances) { outPath = string.Empty; - IntPtr pPathBufferSize = new IntPtr(0); + IntPtr pPathBufferSize = new(0); if (bWildcardInstances) { @@ -852,7 +885,7 @@ private uint MakePath(PDH_COUNTER_PATH_ELEMENTS pathElts, out string outPath, bo try { res = PdhMakeCounterPath(ref pathElts, strPath, ref pPathBufferSize, 0); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { outPath = Marshal.PtrToStringUni(strPath); } @@ -865,14 +898,14 @@ private uint MakePath(PDH_COUNTER_PATH_ELEMENTS pathElts, out string outPath, bo return res; } - private uint MakeAllInstancePath(string origPath, out string unifiedPath) + private static uint MakeAllInstancePath(string origPath, out string unifiedPath) { unifiedPath = origPath; - PDH_COUNTER_PATH_ELEMENTS elts = new PDH_COUNTER_PATH_ELEMENTS(); + PDH_COUNTER_PATH_ELEMENTS elts = new(); uint res = ParsePath(origPath, ref elts); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -880,15 +913,15 @@ private uint MakeAllInstancePath(string origPath, out string unifiedPath) return MakePath(elts, out unifiedPath, true); } - private uint ParsePath(string fullPath, ref PDH_COUNTER_PATH_ELEMENTS pCounterPathElements) + private static uint ParsePath(string fullPath, ref PDH_COUNTER_PATH_ELEMENTS pCounterPathElements) { - IntPtr bufSize = new IntPtr(0); + IntPtr bufSize = new(0); uint res = PdhParseCounterPath(fullPath, IntPtr.Zero, ref bufSize, 0); - if (res != PdhResults.PDH_MORE_DATA && res != 0) + if (res != PdhResults.PDH_MORE_DATA && res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine("error in PdhParseCounterPath: " + res); return res; @@ -902,7 +935,7 @@ private uint ParsePath(string fullPath, ref PDH_COUNTER_PATH_ELEMENTS pCounterPa structPtr, ref bufSize, 0); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { // // Marshal.PtrToStructure will allocate managed memory for the object, @@ -933,11 +966,11 @@ private uint ParsePath(string fullPath, ref PDH_COUNTER_PATH_ELEMENTS pCounterPa // public uint TranslateLocalCounterPath(string englishPath, out string localizedPath) { - uint res = 0; + uint res = PdhResults.PDH_CSTATUS_VALID_DATA; localizedPath = string.Empty; - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); + PDH_COUNTER_PATH_ELEMENTS pathElts = new(); res = ParsePath(englishPath, ref pathElts); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -968,7 +1001,7 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa } catch (Exception) { - return (uint)PdhResults.PDH_INVALID_PATH; + return PdhResults.PDH_INVALID_PATH; } } else if (regString.ToLowerInvariant() == lowerEngObjectName) @@ -979,7 +1012,7 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa } catch (Exception) { - return (uint)PdhResults.PDH_INVALID_PATH; + return PdhResults.PDH_INVALID_PATH; } } @@ -991,13 +1024,13 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa if (counterIndex == -1 || objIndex == -1) { - return (uint)PdhResults.PDH_INVALID_PATH; + return PdhResults.PDH_INVALID_PATH; } // Now, call retrieve the localized names of the object and the counter by index: string objNameLocalized; res = LookupPerfNameByIndex(pathElts.MachineName, (uint)objIndex, out objNameLocalized); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -1006,7 +1039,7 @@ public uint TranslateLocalCounterPath(string englishPath, out string localizedPa string ctrNameLocalized; res = LookupPerfNameByIndex(pathElts.MachineName, (uint)counterIndex, out ctrNameLocalized); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { return res; } @@ -1031,7 +1064,7 @@ public uint LookupPerfNameByIndex(string machineName, uint index, out string loc int strSize = 256; IntPtr localizedPathPtr = Marshal.AllocHGlobal(strSize * sizeof(char)); locName = string.Empty; - uint res = 0; + uint res; try { res = PdhLookupPerfNameByIndex(machineName, index, localizedPathPtr, ref strSize); @@ -1042,7 +1075,7 @@ public uint LookupPerfNameByIndex(string machineName, uint index, out string loc res = PdhLookupPerfNameByIndex(machineName, index, localizedPathPtr, ref strSize); } - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { locName = Marshal.PtrToStringUni(localizedPathPtr); } @@ -1061,9 +1094,7 @@ public uint GetValidPaths(string machineName, ref StringCollection instances, ref StringCollection validPaths) { - uint res = 0; - - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); + PDH_COUNTER_PATH_ELEMENTS pathElts = new(); pathElts.MachineName = machineName; pathElts.ObjectName = objectName; @@ -1095,7 +1126,7 @@ public uint GetValidPaths(string machineName, } } - return res; + return PdhResults.PDH_CSTATUS_VALID_DATA; } public uint AddCounters(ref StringCollection validPaths, bool bFlushOldCounters) @@ -1108,21 +1139,21 @@ public uint AddCounters(ref StringCollection validPaths, bool bFlushOldCounters) } bool bAtLeastOneAdded = false; - uint res = 0; + uint res = PdhResults.PDH_CSTATUS_VALID_DATA; foreach (string counterPath in validPaths) { IntPtr counterHandle; res = PdhAddCounter(_hQuery, counterPath, IntPtr.Zero, out counterHandle); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { - CounterHandleNInstance chi = new CounterHandleNInstance(); + CounterHandleNInstance chi = new(); chi.hCounter = counterHandle; chi.InstanceName = null; - PDH_COUNTER_PATH_ELEMENTS pathElts = new PDH_COUNTER_PATH_ELEMENTS(); + PDH_COUNTER_PATH_ELEMENTS pathElts = new(); res = ParsePath(counterPath, ref pathElts); - if (res == 0 && pathElts.InstanceName != null) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA && pathElts.InstanceName != null) { chi.InstanceName = pathElts.InstanceName.ToLowerInvariant(); } @@ -1136,7 +1167,7 @@ public uint AddCounters(ref StringCollection validPaths, bool bFlushOldCounters) } } - return bAtLeastOneAdded ? 0 : res; + return bAtLeastOneAdded ? PdhResults.PDH_CSTATUS_VALID_DATA : res; } public string GetCounterSetHelp(string szMachineName, string szObjectName) @@ -1149,7 +1180,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi { Debug.Assert(_hQuery != null && !_hQuery.IsInvalid); - uint res = 0; + uint res = PdhResults.PDH_CSTATUS_VALID_DATA; nextSet = null; Int64 batchTimeStampFT = 0; @@ -1160,7 +1191,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi return res; } - if (res != 0 && res != PdhResults.PDH_NO_DATA) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA && res != PdhResults.PDH_NO_DATA) { return res; } @@ -1180,27 +1211,27 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi PerformanceCounterSample[] samplesArr = new PerformanceCounterSample[_consumerPathToHandleAndInstanceMap.Count]; uint sampleIndex = 0; uint numInvalidDataSamples = 0; - uint lastErr = 0; + uint lastErr = PdhResults.PDH_CSTATUS_VALID_DATA; foreach (string path in _consumerPathToHandleAndInstanceMap.Keys) { - IntPtr counterTypePtr = new IntPtr(0); + IntPtr counterTypePtr = new(0); UInt32 counterType = (UInt32)PerformanceCounterType.RawBase; UInt32 defaultScale = 0; UInt64 timeBase = 0; IntPtr hCounter = _consumerPathToHandleAndInstanceMap[path].hCounter; - Debug.Assert(hCounter != null); + Debug.Assert(hCounter != IntPtr.Zero); res = GetCounterInfoPlus(hCounter, out counterType, out defaultScale, out timeBase); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { // Console.WriteLine ("GetCounterInfoPlus for " + path + " failed with " + res); } PDH_RAW_COUNTER rawValue; res = PdhGetRawCounterValue(hCounter, out counterTypePtr, out rawValue); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { samplesArr[sampleIndex++] = new PerformanceCounterSample(path, _consumerPathToHandleAndInstanceMap[path].InstanceName, @@ -1213,7 +1244,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi timeBase, batchStamp, (UInt64)batchStamp.ToFileTime(), - (rawValue.CStatus == 0) ? res : rawValue.CStatus); + (rawValue.CStatus == PdhResults.PDH_CSTATUS_VALID_DATA) ? res : rawValue.CStatus); numInvalidDataSamples++; lastErr = res; @@ -1223,14 +1254,14 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi long dtFT = (((long)rawValue.TimeStamp.dwHighDateTime) << 32) + (uint)rawValue.TimeStamp.dwLowDateTime; - DateTime dt = new DateTime(DateTime.FromFileTimeUtc(dtFT).Ticks, DateTimeKind.Local); + DateTime dt = new(DateTime.FromFileTimeUtc(dtFT).Ticks, DateTimeKind.Local); PDH_FMT_COUNTERVALUE_DOUBLE fmtValueDouble; res = PdhGetFormattedCounterValue(hCounter, PdhFormat.PDH_FMT_DOUBLE | PdhFormat.PDH_FMT_NOCAP100, out counterTypePtr, out fmtValueDouble); - if (res != 0) + if (res != PdhResults.PDH_CSTATUS_VALID_DATA) { samplesArr[sampleIndex++] = new PerformanceCounterSample(path, _consumerPathToHandleAndInstanceMap[path].InstanceName, @@ -1243,7 +1274,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi timeBase, dt, (UInt64)dtFT, - (fmtValueDouble.CStatus == 0) ? res : rawValue.CStatus); + (fmtValueDouble.CStatus == PdhResults.PDH_CSTATUS_VALID_DATA) ? res : rawValue.CStatus); numInvalidDataSamples++; lastErr = res; @@ -1276,7 +1307,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi // // Reset the error - any errors are saved per sample in PerformanceCounterSample.Status for kvetching later // - res = 0; + res = PdhResults.PDH_CSTATUS_VALID_DATA; } return res; @@ -1285,7 +1316,7 @@ public uint ReadNextSet(out PerformanceCounterSampleSet nextSet, bool bSkipReadi public uint ExpandWildCardPath(string path, out StringCollection expandedPaths) { expandedPaths = new StringCollection(); - IntPtr pcchPathListLength = new IntPtr(0); + IntPtr pcchPathListLength = new(0); uint res = PdhExpandWildCardPathH(_hDataSource, path, @@ -1304,7 +1335,7 @@ public uint ExpandWildCardPath(string path, out StringCollection expandedPaths) try { res = PdhExpandWildCardPathH(_hDataSource, path, strPathList, ref pcchPathListLength, PdhWildCardFlag.PDH_REFRESHCOUNTERS); - if (res == 0) + if (res == PdhResults.PDH_CSTATUS_VALID_DATA) { ReadPdhMultiString(ref strPathList, pcchPathListLength.ToInt32(), ref expandedPaths); } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs index 39aded7d646..2fc58cc00e1 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhSafeHandle.cs @@ -4,8 +4,6 @@ using System; using System.Runtime.InteropServices; -using System.Runtime.ConstrainedExecution; - namespace Microsoft.Powershell.Commands.GetCounter.PdhNative { internal sealed class PdhSafeDataSourceHandle : SafeHandle @@ -20,7 +18,6 @@ public override bool IsInvalid } } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return (PdhHelper.PdhCloseLog(handle, 0) == 0); @@ -39,7 +36,6 @@ public override bool IsInvalid } } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return (PdhHelper.PdhCloseQuery(handle) == 0); @@ -58,7 +54,6 @@ public override bool IsInvalid } } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] protected override bool ReleaseHandle() { return (PdhHelper.PdhCloseLog(handle, 0) == 0); diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx index f8f41ecc2f5..5c66f2b9e94 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx @@ -255,6 +255,12 @@ The defined template is following: To access the '{0}' log start PowerShell with elevated user rights. Error: {1} + + Access denied for log: '{0}'. + + + Launch PowerShell with elevated user rights. + Cannot retrieve event message text. @@ -285,4 +291,25 @@ The defined template is following: Log count ({0}) is exceeded Windows Event Log API limit ({1}). Adjust filter to return less log names. + + Specifies the event logs. Wildcards are permitted. + + + Specifies the event logs that this cmdlet gets events from. Wildcards are permitted. + + + Specifies the event log providers that this cmdlet gets. + + + Specifies the event log providers from which this cmdlet gets events. + + + Specifies the path to the event log files that this cmdlet gets events from. + + + Specifies the maximum number of events that are returned. + + + Specifies the name of the computer from which this cmdlet gets data. + 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 00304d6fde1..8ce2a97d67b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -2,7 +2,7 @@ PowerShell's Microsoft.PowerShell.Commands.Management project - $(NoWarn);CS1570 + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.Commands.Management @@ -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 9ad44f3a07f..3e26b2e2e98 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/SessionBasedWrapper.cs @@ -75,15 +75,14 @@ protected virtual void Dispose(bool disposing) [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] protected TSession[] Session { - get { return _session ?? (_session = new TSession[] { this.DefaultSession }); } + get + { + return _session ??= new TSession[] { this.DefaultSession }; + } set { - if (value == null) - { - throw new ArgumentNullException("value"); - } - + ArgumentNullException.ThrowIfNull(value); _session = value; _sessionWasSpecified = true; } @@ -126,9 +125,9 @@ public SwitchParameter AsJob /// Doing so will interfere with ThrottleLimit functionality. /// /// - /// (and other methods returning job results) will block to support throttling and flow-control. + /// (and other methods returning job results) will block to support throttling and flow-control. /// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further processing, - /// until calls from (and other methods returning job results) return. + /// until calls from (and other methods returning job results) return. /// /// internal abstract StartableJob CreateQueryJob(TSession session, QueryBuilder query); @@ -151,14 +150,15 @@ private StartableJob DoCreateQueryJob(TSession sessionForJob, QueryBuilder query discardNonPipelineResults, actionAgainstResults == null ? (Action)null - : delegate (PSObject pso) - { - var objectInstance = - (TObjectInstance) - LanguagePrimitives.ConvertTo(pso, typeof(TObjectInstance), - CultureInfo.InvariantCulture); - actionAgainstResults(sessionForJob, objectInstance); - }); + : ((PSObject pso) => + { + var objectInstance = + (TObjectInstance)LanguagePrimitives.ConvertTo( + pso, + typeof(TObjectInstance), + CultureInfo.InvariantCulture); + actionAgainstResults(sessionForJob, objectInstance); + })); } return queryJob; @@ -170,16 +170,16 @@ private StartableJob DoCreateQueryJob(TSession sessionForJob, QueryBuilder query /// Remote session to invoke the method in. /// The object on which to invoke the method. /// Method invocation details. - /// true if successful method invocations should emit downstream the being operated on. + /// if successful method invocations should emit downstream the being operated on. /// /// /// This method shouldn't do any processing or interact with the remote session. /// Doing so will interfere with ThrottleLimit functionality. /// /// - /// (and other methods returning job results) will block to support throttling and flow-control. + /// (and other methods returning job results) will block to support throttling and flow-control. /// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further processing, - /// until calls from (and other methods returning job results) return. + /// until calls from (and other methods returning job results) return. /// /// internal abstract StartableJob CreateInstanceMethodInvocationJob(TSession session, TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru); @@ -212,9 +212,9 @@ private StartableJob DoCreateInstanceMethodInvocationJob(TSession sessionForJob, /// Doing so will interfere with ThrottleLimit functionality. /// /// - /// (and other methods returning job results) will block to support throttling and flow-control. + /// (and other methods returning job results) will block to support throttling and flow-control. /// Implementations of Job instance returned from this method should make sure that implementation-specific flow-control mechanism pauses further processing, - /// until calls from (and other methods returning job results) return. + /// until calls from (and other methods returning job results) return. /// /// internal abstract StartableJob CreateStaticMethodInvocationJob(TSession session, MethodInvocationInfo methodInvocationInfo); @@ -236,24 +236,21 @@ private StartableJob DoCreateStaticMethodInvocationJob(TSession sessionForJob, M return methodInvocationJob; } - private void HandleJobOutput(Job job, TSession sessionForJob, bool discardNonPipelineResults, Action outputAction) + private static void HandleJobOutput(Job job, TSession sessionForJob, bool discardNonPipelineResults, Action outputAction) { Action processOutput = - delegate (PSObject pso) + (PSObject pso) => { if (pso == null) { return; } - if (outputAction != null) - { - outputAction(pso); - } + outputAction?.Invoke(pso); }; job.Output.DataAdded += - delegate (object sender, DataAddedEventArgs eventArgs) + (object sender, DataAddedEventArgs eventArgs) => { var dataCollection = (PSDataCollection)sender; @@ -303,7 +300,7 @@ internal virtual TSession GetSessionOfOriginFromInstance(TObjectInstance instanc private static void DiscardJobOutputs(PSDataCollection psDataCollection) { psDataCollection.DataAdded += - delegate (object sender, DataAddedEventArgs e) + (object sender, DataAddedEventArgs e) => { var localDataCollection = (PSDataCollection)sender; localDataCollection.Clear(); @@ -327,37 +324,37 @@ private enum JobOutputs private static void DiscardJobOutputs(Job job, JobOutputs jobOutputsToDiscard) { - if (JobOutputs.Output == (jobOutputsToDiscard & JobOutputs.Output)) + if ((jobOutputsToDiscard & JobOutputs.Output) == JobOutputs.Output) { DiscardJobOutputs(job.Output); } - if (JobOutputs.Error == (jobOutputsToDiscard & JobOutputs.Error)) + if ((jobOutputsToDiscard & JobOutputs.Error) == JobOutputs.Error) { DiscardJobOutputs(job.Error); } - if (JobOutputs.Warning == (jobOutputsToDiscard & JobOutputs.Warning)) + if ((jobOutputsToDiscard & JobOutputs.Warning) == JobOutputs.Warning) { DiscardJobOutputs(job.Warning); } - if (JobOutputs.Verbose == (jobOutputsToDiscard & JobOutputs.Verbose)) + if ((jobOutputsToDiscard & JobOutputs.Verbose) == JobOutputs.Verbose) { DiscardJobOutputs(job.Verbose); } - if (JobOutputs.Debug == (jobOutputsToDiscard & JobOutputs.Debug)) + if ((jobOutputsToDiscard & JobOutputs.Debug) == JobOutputs.Debug) { DiscardJobOutputs(job.Debug); } - if (JobOutputs.Progress == (jobOutputsToDiscard & JobOutputs.Progress)) + if ((jobOutputsToDiscard & JobOutputs.Progress) == JobOutputs.Progress) { DiscardJobOutputs(job.Progress); } - if (JobOutputs.Results == (jobOutputsToDiscard & JobOutputs.Results)) + if ((jobOutputsToDiscard & JobOutputs.Results) == JobOutputs.Results) { DiscardJobOutputs(job.Results); } @@ -399,7 +396,7 @@ public override void ProcessRecord(QueryBuilder query) /// /// Query parameters. /// Method invocation details. - /// true if successful method invocations should emit downstream the object instance being operated on. + /// if successful method invocations should emit downstream the object instance being operated on. public override void ProcessRecord(QueryBuilder query, MethodInvocationInfo methodInvocationInfo, bool passThru) { _parentJob.DisableFlowControlForPendingJobsQueue(); @@ -412,7 +409,7 @@ public override void ProcessRecord(QueryBuilder query, MethodInvocationInfo meth StartableJob queryJob = this.DoCreateQueryJob( sessionForJob, query, - delegate (TSession sessionForMethodInvocationJob, TObjectInstance objectInstance) + (TSession sessionForMethodInvocationJob, TObjectInstance objectInstance) => { StartableJob methodInvocationJob = this.DoCreateInstanceMethodInvocationJob( sessionForMethodInvocationJob, @@ -494,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) @@ -578,11 +574,12 @@ private TSession GetImpliedSession() /// /// The object on which to invoke the method. /// Method invocation details. - /// true if successful method invocations should emit downstream the being operated on. + /// 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)) { @@ -607,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)) { @@ -687,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 092103d6fe7..935cc960c6c 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 @@ -90,7 +69,7 @@ internal static CimJobException CreateFromCimException( Dbg.Assert(cimException != null, "Caller should verify cimException != null"); string message = BuildErrorMessage(jobDescription, jobContext, cimException.Message); - CimJobException cimJobException = new CimJobException(message, cimException); + CimJobException cimJobException = new(message, cimException); cimJobException.InitializeErrorRecord(jobContext, cimException); return cimJobException; } @@ -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 CimJobException(message, inner); - var containsErrorRecord = inner as IContainsErrorRecord; - if (containsErrorRecord != null) + CimJobException cimJobException = new(message, inner); + if (inner is IContainsErrorRecord containsErrorRecord) { cimJobException.InitializeErrorRecord( jobContext, @@ -142,7 +119,7 @@ internal static CimJobException CreateWithFullControl( Dbg.Assert(!string.IsNullOrEmpty(message), "Caller should verify message != null"); Dbg.Assert(!string.IsNullOrEmpty(errorId), "Caller should verify errorId != null"); - CimJobException cimJobException = new CimJobException(jobContext.PrependComputerNameToMessage(message), inner); + CimJobException cimJobException = new(jobContext.PrependComputerNameToMessage(message), inner); cimJobException.InitializeErrorRecord(jobContext, errorId, errorCategory); return cimJobException; } @@ -156,7 +133,7 @@ internal static CimJobException CreateWithoutJobContext( Dbg.Assert(!string.IsNullOrEmpty(message), "Caller should verify message != null"); Dbg.Assert(!string.IsNullOrEmpty(errorId), "Caller should verify errorId != null"); - CimJobException cimJobException = new CimJobException(message, inner); + CimJobException cimJobException = new(message, inner); cimJobException.InitializeErrorRecord(null, errorId, errorCategory); return cimJobException; } @@ -170,7 +147,7 @@ internal static CimJobException CreateFromMethodErrorCode(string jobDescription, string errorMessage = BuildErrorMessage(jobDescription, jobContext, rawErrorMessage); - CimJobException cje = new CimJobException(errorMessage); + CimJobException cje = new(errorMessage); cje.InitializeErrorRecord(jobContext, "CimJob_" + methodName + "_" + errorCodeFromMethod, ErrorCategory.InvalidResult); return cje; @@ -197,15 +174,15 @@ private static string BuildErrorMessage(string jobDescription, CimJobContext job private void InitializeErrorRecordCore(CimJobContext jobContext, Exception exception, string errorId, ErrorCategory errorCategory) { - ErrorRecord coreErrorRecord = new ErrorRecord( + ErrorRecord coreErrorRecord = new( exception: exception, errorId: errorId, errorCategory: errorCategory, - targetObject: jobContext != null ? jobContext.TargetObject : null); + targetObject: jobContext?.TargetObject); if (jobContext != null) { - System.Management.Automation.Remoting.OriginInfo originInfo = new System.Management.Automation.Remoting.OriginInfo( + System.Management.Automation.Remoting.OriginInfo originInfo = new( jobContext.Session.ComputerName, Guid.Empty); @@ -242,7 +219,7 @@ private void InitializeErrorRecord(CimJobContext jobContext, CimException cimExc if (cimException.ErrorData != null) { _errorRecord.CategoryInfo.TargetName = cimException.ErrorSource; - _errorRecord.CategoryInfo.TargetType = jobContext != null ? jobContext.CmdletizationClassName : null; + _errorRecord.CategoryInfo.TargetType = jobContext?.CmdletizationClassName; } } @@ -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/CreateInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs index 7f76ff98574..eb594dd4eea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job wrapping invocation of a CreateInstance intrinsic CIM method. /// - internal class CreateInstanceJob : PropertySettingJob + internal sealed class CreateInstanceJob : PropertySettingJob { private CimInstance _resultFromCreateInstance; private CimInstance _resultFromGetInstance; @@ -67,8 +67,8 @@ internal override IObservable GetCimOperation() } #if DEBUG - Dbg.Assert(_getInstanceOperationGotStarted == false, "CreateInstance should be started *before* GetInstance"); - Dbg.Assert(_createInstanceOperationGotStarted == false, "Should not start CreateInstance operation twice"); + Dbg.Assert(!_getInstanceOperationGotStarted, "CreateInstance should be started *before* GetInstance"); + Dbg.Assert(!_createInstanceOperationGotStarted, "Should not start CreateInstance operation twice"); _createInstanceOperationGotStarted = true; #endif return GetCreateInstanceOperation(); @@ -77,7 +77,7 @@ internal override IObservable GetCimOperation() { #if DEBUG Dbg.Assert(_createInstanceOperationGotStarted, "GetInstance should be started *after* CreateInstance"); - Dbg.Assert(_getInstanceOperationGotStarted == false, "Should not start GetInstance operation twice"); + Dbg.Assert(!_getInstanceOperationGotStarted, "Should not start GetInstance operation twice"); Dbg.Assert(_resultFromGetInstance == null, "GetInstance operation shouldn't happen twice"); _getInstanceOperationGotStarted = true; #endif diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs index 327fb83affa..0432c6c9a8f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job wrapping invocation of a DeleteInstance intrinsic CIM method. /// - internal class DeleteInstanceJob : MethodInvocationJobBase + internal sealed class DeleteInstanceJob : MethodInvocationJobBase { private readonly CimInstance _objectToDelete; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs index bd024b7d76a..569740d0d1b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server. /// - internal class EnumerateAssociatedInstancesJob : QueryJobBase + internal sealed class EnumerateAssociatedInstancesJob : QueryJobBase { private readonly CimInstance _associatedObject; private readonly string _associationName; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs index 3a8b0e2200c..9eaac2b3d7f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ExtrinsicMethodInvocationJob.cs @@ -52,22 +52,21 @@ private void ProcessOutParameter(CimMethodResult methodResult, MethodParameter m { Dbg.Assert(methodResult != null, "Caller should verify methodResult != null"); Dbg.Assert(methodParameter != null, "Caller should verify methodParameter != null"); - Dbg.Assert(0 != (methodParameter.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)), "Caller should verify that this is an out parameter"); + Dbg.Assert((methodParameter.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)) != 0, "Caller should verify that this is an out parameter"); Dbg.Assert(cmdletOutput != null, "Caller should verify cmdletOutput != null"); Dbg.Assert(this.MethodSubject != null, "MethodSubject property should be initialized before starting main job processing"); CimMethodParameter outParameter = methodResult.OutParameters[methodParameter.Name]; - object valueReturnedFromMethod = (outParameter == null) ? null : outParameter.Value; + object valueReturnedFromMethod = outParameter?.Value; object dotNetValue = CimValueConverter.ConvertFromCimToDotNet(valueReturnedFromMethod, methodParameter.ParameterType); - if (MethodParameterBindings.Out == (methodParameter.Bindings & MethodParameterBindings.Out)) + if ((methodParameter.Bindings & MethodParameterBindings.Out) == MethodParameterBindings.Out) { 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,13 +74,12 @@ 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); } } - else if (MethodParameterBindings.Error == (methodParameter.Bindings & MethodParameterBindings.Error)) + else if ((methodParameter.Bindings & MethodParameterBindings.Error) == MethodParameterBindings.Error) { var gotError = (bool)LanguagePrimitives.ConvertTo(dotNetValue, typeof(bool), CultureInfo.InvariantCulture); if (gotError) @@ -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/InstanceMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs index cd1eb78d81c..06a45933522 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job wrapping invocation of an extrinsic CIM method. /// - internal class InstanceMethodInvocationJob : ExtrinsicMethodInvocationJob + internal sealed class InstanceMethodInvocationJob : ExtrinsicMethodInvocationJob { private readonly CimInstance _targetInstance; 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 075e218ea60..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 => (0 != (p.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)))); + .Where(static p => ((p.Bindings & (MethodParameterBindings.Out | MethodParameterBindings.Error)) != 0)); return outParameters; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs index f0d07fc201d..869002a55f4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job wrapping invocation of a ModifyInstance intrinsic CIM method. /// - internal class ModifyInstanceJob : PropertySettingJob + internal sealed class ModifyInstanceJob : PropertySettingJob { private CimInstance _resultFromModifyInstance; private bool _resultFromModifyInstanceHasBeenPassedThru; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs index 4297c00616c..7bd2bd17531 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server. /// - internal class QueryInstancesJob : QueryJobBase + internal sealed class QueryInstancesJob : QueryJobBase { private readonly string _wqlQuery; private readonly bool _useEnumerateInstances; @@ -27,7 +27,7 @@ internal QueryInstancesJob(CimJobContext jobContext, CimQuery cimQuery, string w var wqlQueryBuilder = new StringBuilder(); wqlQueryBuilder.Append("SELECT * FROM "); wqlQueryBuilder.Append(this.JobContext.ClassName); - wqlQueryBuilder.Append(" "); + wqlQueryBuilder.Append(' '); wqlQueryBuilder.Append(wqlCondition); _wqlQuery = wqlQueryBuilder.ToString(); diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs index 8ee336de383..b52c23c3dd4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJobBase.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// internal abstract class QueryJobBase : CimChildJobBase { - private CimQuery _cimQuery; + private readonly CimQuery _cimQuery; internal QueryJobBase(CimJobContext jobContext, CimQuery cimQuery) : base(jobContext) diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs index 5a25206fe94..3bc376ab3d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// Job wrapping invocation of a static CIM method. /// - internal class StaticMethodInvocationJob : ExtrinsicMethodInvocationJob + internal sealed class StaticMethodInvocationJob : ExtrinsicMethodInvocationJob { internal StaticMethodInvocationJob(CimJobContext jobContext, MethodInvocationInfo methodInvocationInfo) : base(jobContext, false /* passThru */, jobContext.CmdletizationClassName, methodInvocationInfo) diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/TerminatingErrorTracker.cs index 730e54a6391..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,12 +19,12 @@ 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 private static readonly ConditionalWeakTable s_invocationToTracker = - new ConditionalWeakTable(); + new(); private static int GetNumberOfSessions(InvocationInfo invocationInfo) { @@ -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) @@ -100,7 +99,7 @@ private TerminatingErrorTracker(int numberOfSessions) #region Tracking session's "connectivity" status - private readonly ConcurrentDictionary _sessionToIsConnected = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _sessionToIsConnected = new(); internal void MarkSessionAsConnected(CimSession connectedSession) { @@ -166,7 +165,7 @@ internal Exception GetExceptionIfBrokenSession( #region Tracking session's "terminated" status - private readonly ConcurrentDictionary _sessionToIsTerminated = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _sessionToIsTerminated = new(); internal void MarkSessionAsTerminated(CimSession terminatedSession, out bool sessionWasAlreadyTerminated) { @@ -175,11 +174,11 @@ internal void MarkSessionAsTerminated(CimSession terminatedSession, out bool ses key: terminatedSession, addValue: true, updateValueFactory: - delegate (CimSession key, bool isTerminatedValueInDictionary) - { - closureSafeSessionWasAlreadyTerminated = isTerminatedValueInDictionary; - return true; - }); + (CimSession key, bool isTerminatedValueInDictionary) => + { + closureSafeSessionWasAlreadyTerminated = isTerminatedValueInDictionary; + return true; + }); sessionWasAlreadyTerminated = closureSafeSessionWasAlreadyTerminated; } @@ -196,22 +195,22 @@ internal bool IsSessionTerminated(CimSession session) internal CmdletMethodInvoker GetErrorReportingDelegate(ErrorRecord errorRecord) { - ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim(); - object lockObject = new object(); - Func action = delegate (Cmdlet cmdlet) - { - _numberOfReportedSessionTerminatingErrors++; - if (_numberOfReportedSessionTerminatingErrors >= _numberOfSessions) - { - cmdlet.ThrowTerminatingError(errorRecord); - } - else - { - cmdlet.WriteError(errorRecord); - } - - return false; // not really needed here, but required by CmdletMethodInvoker - }; + ManualResetEventSlim manualResetEventSlim = new(); + object lockObject = new(); + Func action = (Cmdlet cmdlet) => + { + _numberOfReportedSessionTerminatingErrors++; + if (_numberOfReportedSessionTerminatingErrors >= _numberOfSessions) + { + cmdlet.ThrowTerminatingError(errorRecord); + } + else + { + cmdlet.WriteError(errorRecord); + } + + return false; // not really needed here, but required by CmdletMethodInvoker + }; return new CmdletMethodInvoker { 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 3743d1965f0..6bd2bee7fee 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs @@ -57,7 +57,7 @@ internal CimChildJobBase(CimJobContext jobContext) _jobSpecificCustomOptions = new Lazy(this.CalculateJobSpecificCustomOptions); } - private readonly CimSensitiveValueConverter _cimSensitiveValueConverter = new CimSensitiveValueConverter(); + private readonly CimSensitiveValueConverter _cimSensitiveValueConverter = new(); internal CimSensitiveValueConverter CimSensitiveValueConverter { get { return _cimSensitiveValueConverter; } } @@ -84,8 +84,7 @@ private enum WsManErrorCode : uint private static bool IsWsManQuotaReached(Exception exception) { - var cimException = exception as CimException; - if (cimException == null) + if (exception is not CimException cimException) { return false; } @@ -112,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: @@ -159,7 +158,7 @@ public virtual void OnCompleted() }); } - private static readonly Random s_globalRandom = new Random(); + private static readonly Random s_globalRandom = new(); private readonly Random _random; private int _sleepAndRetryDelayRangeMs = 1000; private int _sleepAndRetryExtraDelayMs = 0; @@ -316,10 +315,7 @@ internal override void StartJob() this.ExceptionSafeWrapper(delegate { IObservable observable = this.GetCimOperation(); - if (observable != null) - { - observable.Subscribe(this); - } + observable?.Subscribe(this); }); }); } @@ -424,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 (( @@ -436,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 (( @@ -448,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) @@ -523,10 +519,7 @@ internal CimOperationOptions CreateOperationOptions() } CimCustomOptionsDictionary jobSpecificCustomOptions = this.GetJobSpecificCustomOptions(); - if (jobSpecificCustomOptions != null) - { - jobSpecificCustomOptions.Apply(operationOptions, CimSensitiveValueConverter); - } + jobSpecificCustomOptions?.Apply(operationOptions, CimSensitiveValueConverter); return operationOptions; } @@ -544,7 +537,7 @@ private CimCustomOptionsDictionary GetJobSpecificCustomOptions() #region Controlling job state - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); /// /// Stops this job. @@ -579,7 +572,7 @@ public override void StopJob() _cancellationTokenSource.Cancel(); } - private readonly object _jobStateLock = new object(); + private readonly object _jobStateLock = new(); private bool _jobHadErrors; private bool _jobWasStarted; private bool _jobWasStopped; @@ -630,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; @@ -731,7 +723,7 @@ internal void SetCompletedJobState(JobState state, Exception reason) #region Support for progress reporting - private readonly ConcurrentDictionary _activityIdToLastProgressRecord = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _activityIdToLastProgressRecord = new(); internal override void WriteProgress(ProgressRecord progressRecord) { @@ -764,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)) { @@ -776,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 { @@ -826,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 @@ -1010,8 +1002,7 @@ private CimResponseType PromptUserCallback(string message, CimPromptType promptT internal static bool IsShowComputerNameMarkerPresent(CimInstance cimInstance) { PSObject pso = PSObject.AsPSObject(cimInstance); - PSPropertyInfo psShowComputerNameProperty = pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] as PSPropertyInfo; - if (psShowComputerNameProperty == null) + if (pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is not PSPropertyInfo psShowComputerNameProperty) { return false; } @@ -1021,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; } @@ -1055,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) @@ -1087,6 +1074,7 @@ protected override void Dispose(bool disposing) } _cimSensitiveValueConverter.Dispose(); + _cancellationTokenSource.Dispose(); } } } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs index 6fa40516322..8a4e4272561 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletDefinitionContext + internal sealed class CimCmdletDefinitionContext { internal CimCmdletDefinitionContext( string cmdletizationClassName, @@ -26,13 +26,13 @@ internal CimCmdletDefinitionContext( _privateData = privateData; } - public string CmdletizationClassName { get; private set; } + public string CmdletizationClassName { get; } - public string CmdletizationClassVersion { get; private set; } + public string CmdletizationClassVersion { get; } - public Version CmdletizationModuleVersion { get; private set; } + public Version CmdletizationModuleVersion { get; } - public bool SupportsShouldProcess { get; private set; } + public bool SupportsShouldProcess { get; } private readonly IDictionary _privateData; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs index cfa26ca0e05..3bee74526fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletInvocationContext + internal sealed class CimCmdletInvocationContext { internal CimCmdletInvocationContext( CimCmdletDefinitionContext cmdletDefinitionContext, @@ -76,26 +76,26 @@ private static void WarnAboutUnsupportedActionPreferences( if (actionPreferenceComesFromCommandLineParameter) { Exception exception = new ArgumentException(message); - ErrorRecord errorRecord = new ErrorRecord(exception, "ActionPreferenceNotSupportedByCimCmdletAdapter", ErrorCategory.NotImplemented, null); + ErrorRecord errorRecord = new(exception, "ActionPreferenceNotSupportedByCimCmdletAdapter", ErrorCategory.NotImplemented, null); cmdlet.ThrowTerminatingError(errorRecord); } } - public CimCmdletDefinitionContext CmdletDefinitionContext { get; private set; } + public CimCmdletDefinitionContext CmdletDefinitionContext { get; } - public InvocationInfo CmdletInvocationInfo { get; private set; } + public InvocationInfo CmdletInvocationInfo { get; } - public MshCommandRuntime.ShouldProcessPossibleOptimization ShouldProcessOptimization { get; private set; } + public MshCommandRuntime.ShouldProcessPossibleOptimization ShouldProcessOptimization { get; } - public ActionPreference ErrorActionPreference { get; private set; } + public ActionPreference ErrorActionPreference { get; } - public ActionPreference WarningActionPreference { get; private set; } + public ActionPreference WarningActionPreference { get; } - public ActionPreference VerboseActionPreference { get; private set; } + public ActionPreference VerboseActionPreference { get; } - public ActionPreference DebugActionPreference { get; private set; } + public ActionPreference DebugActionPreference { get; } - public string NamespaceOverride { get; private set; } + public string NamespaceOverride { get; } public bool IsRunningInBackground { @@ -113,7 +113,7 @@ public bool ShowComputerName } } - private readonly Lazy _defaultCimSession = new Lazy(CreateDefaultCimSession); + private readonly Lazy _defaultCimSession = new(CreateDefaultCimSession); private static CimSession CreateDefaultCimSession() { 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 033b8813534..da075286c34 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs @@ -24,9 +24,9 @@ namespace Microsoft.PowerShell.Cim { - internal class CimSensitiveValueConverter : IDisposable + internal sealed 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) { @@ -129,7 +123,7 @@ private void Dispose(bool disposing) } } - private readonly List _trackedDisposables = new List(); + private readonly List _trackedDisposables = new(); /// /// Releases resources associated with this object. @@ -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) { @@ -399,21 +393,21 @@ internal static object ConvertFromCimToDotNet(object cimObject, Type expectedDot return dotNetObject; } - Func, object> exceptionSafeReturn = delegate (Func innerAction) - { - try - { - return innerAction(); - } - catch (Exception e) - { - throw CimValueConverter.GetInvalidCastException( - e, - "InvalidCimToDotNetCast", - cimObject, - expectedDotNetType.FullName); - } - }; + Func, object> exceptionSafeReturn = (Func innerAction) => + { + try + { + return innerAction(); + } + catch (Exception e) + { + throw CimValueConverter.GetInvalidCastException( + e, + "InvalidCimToDotNetCast", + cimObject, + expectedDotNetType.FullName); + } + }; if (typeof(ObjectSecurity).IsAssignableFrom(expectedDotNetType)) { @@ -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/cimJobContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs index 5dcca85a9b9..27d48ccab9a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimJobContext + internal sealed class CimJobContext { internal CimJobContext( CimCmdletInvocationContext cmdletInvocationContext, @@ -22,11 +22,11 @@ internal CimJobContext( this.TargetObject = targetObject ?? this.ClassName; } - public CimCmdletInvocationContext CmdletInvocationContext { get; private set; } + public CimCmdletInvocationContext CmdletInvocationContext { get; } - public CimSession Session { get; private set; } + public CimSession Session { get; } - public object TargetObject { get; private set; } + public object TargetObject { get; } public string ClassName { 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 17801923bf0..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,10 +14,10 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCustomOptionsDictionary + internal sealed class CimCustomOptionsDictionary { private readonly IDictionary _dict; - private readonly object _dictModificationLock = new object(); + private readonly object _dictModificationLock = new(); private CimCustomOptionsDictionary(IEnumerable> wrappedDictionary) { @@ -42,7 +42,7 @@ internal static CimCustomOptionsDictionary Create(IEnumerable s_cimInstanceToCustomOptions = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable s_cimInstanceToCustomOptions = new(); internal static void AssociateCimInstanceWithCustomOptions(CimInstance cimInstance, CimCustomOptionsDictionary newCustomOptions) { 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 551b2149e41..f08c2ab3c11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs @@ -18,7 +18,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// /// CimQuery supports building of queries against CIM object model. /// - internal class CimQuery : QueryBuilder, ISessionBoundQueryBuilder + internal sealed class CimQuery : QueryBuilder, ISessionBoundQueryBuilder { private readonly StringBuilder _wqlCondition; @@ -27,9 +27,9 @@ internal class CimQuery : QueryBuilder, ISessionBoundQueryBuilder private string _resultRole; private string _sourceRole; - internal readonly Dictionary queryOptions = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal readonly Dictionary queryOptions = new(StringComparer.OrdinalIgnoreCase); - internal ClientSideQuery ClientSideQuery { get; private set; } + internal ClientSideQuery ClientSideQuery { get; } internal CimQuery() { @@ -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,8 +196,8 @@ private static string GetMatchCondition(string propertyName, IEnumerable propert /// Property name to query on. /// Property values to accept in the query. /// - /// true if should be treated as a containing a wildcard pattern; - /// false otherwise + /// if should be treated as a containing a wildcard pattern; + /// otherwise. /// /// /// Describes how to handle filters that didn't match any objects @@ -219,8 +219,8 @@ public override void FilterByProperty(string propertyName, IEnumerable allowedPr /// Property name to query on. /// Property values to reject in the query. /// - /// true if should be treated as a containing a wildcard pattern; - /// false otherwise + /// if should be treated as a containing a wildcard pattern; + /// otherwise. /// /// /// Describes how to handle filters that didn't match any objects @@ -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 a667e6da2f5..472046b192f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs @@ -94,11 +94,10 @@ internal CimCmdletInvocationContext CmdletInvocationContext { get { - return _cmdletInvocationContext ?? - (_cmdletInvocationContext = new CimCmdletInvocationContext( - this.CmdletDefinitionContext, - this.Cmdlet, - this.GetDynamicNamespace())); + return _cmdletInvocationContext ??= new CimCmdletInvocationContext( + this.CmdletDefinitionContext, + this.Cmdlet, + this.GetDynamicNamespace()); } } @@ -108,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; } @@ -172,8 +168,7 @@ private CimJobContext CreateJobContext(CimSession session, object targetObject) /// object that performs a query against the wrapped object model. internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder baseQuery) { - CimQuery query = baseQuery as CimQuery; - if (query == null) + if (baseQuery is not CimQuery query) { throw new ArgumentNullException(nameof(baseQuery)); } @@ -201,7 +196,7 @@ internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder b /// Remote session to invoke the method in. /// The object on which to invoke the method. /// Method invocation details. - /// true if successful method invocations should emit downstream the being operated on. + /// if successful method invocations should emit downstream the being operated on. /// internal override StartableJob CreateInstanceMethodInvocationJob(CimSession session, CimInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru) { @@ -283,7 +278,7 @@ private bool IsSupportedSession(CimSession cimSession, TerminatingErrorTracker t cimSession.ComputerName, nameOfUnsupportedSwitch); Exception exception = new NotSupportedException(errorMessage); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( exception, "NoExtendedSemanticsSupportInRemoteDcomProtocol", ErrorCategory.NotImplemented, @@ -340,7 +335,7 @@ internal override StartableJob CreateStaticMethodInvocationJob(CimSession sessio #region Session affinity management - private static readonly ConditionalWeakTable s_cimInstanceToSessionOfOrigin = new ConditionalWeakTable(); + private static readonly ConditionalWeakTable s_cimInstanceToSessionOfOrigin = new(); internal static void AssociateSessionOfOriginWithInstance(CimInstance cimInstance, CimSession sessionOfOrigin) { @@ -396,10 +391,10 @@ object IDynamicParameters.GetDynamicParameters() if (this.CmdletDefinitionContext.ExposeCimNamespaceParameter) { - Collection namespaceAttributes = new Collection(); + Collection namespaceAttributes = new(); namespaceAttributes.Add(new ValidateNotNullOrEmptyAttribute()); namespaceAttributes.Add(new ParameterAttribute()); - RuntimeDefinedParameter namespaceRuntimeParameter = new RuntimeDefinedParameter( + RuntimeDefinedParameter namespaceRuntimeParameter = new( CimNamespaceParameter, typeof(string), namespaceAttributes); 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 001b311e82a..c1a8552db8c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs @@ -20,9 +20,9 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// 1) filtering that cannot be translated into a server-side query (i.e. when CimQuery.WildcardToWqlLikeOperand reports that it cannot translate into WQL) /// 2) detecting if all expected results have been received and giving friendly user errors otherwise (i.e. could not find process with name='foo'; details in Windows 8 Bugs: #60926) /// - internal class ClientSideQuery : QueryBuilder + internal sealed class ClientSideQuery : QueryBuilder { - internal class NotFoundError + internal sealed class NotFoundError { public NotFoundError() { @@ -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); @@ -55,11 +54,11 @@ public NotFoundError(string propertyName, object propertyValue, bool wildcardsEn } } - public string PropertyName { get; private set; } + public string PropertyName { get; } - public object PropertyValue { get; private set; } + public object PropertyValue { get; } - public Func ErrorMessageGenerator { get; private set; } + public Func ErrorMessageGenerator { get; } private static string GetErrorMessageForNotFound(string queryDescription, string className) { @@ -153,7 +152,7 @@ public virtual IEnumerable GetNotFoundErrors_IfThisIsTheOnlyFilte private abstract class CimInstancePropertyBasedFilter : CimInstanceFilterBase { - private readonly List _propertyValueFilters = new List(); + private readonly List _propertyValueFilters = new(); protected IEnumerable PropertyValueFilters { get { return _propertyValueFilters; } } @@ -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) { @@ -405,7 +404,7 @@ public bool IsMatch(CimInstance o) private object ConvertActualValueToExpectedType(object actualPropertyValue, object expectedPropertyValue) { - if ((actualPropertyValue is string) && (!(expectedPropertyValue is string))) + if (actualPropertyValue is string && expectedPropertyValue is not string) { actualPropertyValue = LanguagePrimitives.ConvertTo(actualPropertyValue, expectedPropertyValue.GetType(), CultureInfo.InvariantCulture); } @@ -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); @@ -533,7 +530,7 @@ private static bool WildcardEqual(string propertyName, object actualPropertyValu } } - internal class PropertyValueExcludeFilter : PropertyValueRegularFilter + internal sealed class PropertyValueExcludeFilter : PropertyValueRegularFilter { public PropertyValueExcludeFilter(string propertyName, object expectedPropertyValue, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, wildcardsEnabled, behaviorOnNoMatch) @@ -551,7 +548,7 @@ protected override bool IsMatchingValue(object actualPropertyValue) } } - internal class PropertyValueMinFilter : PropertyValueFilter + internal sealed class PropertyValueMinFilter : PropertyValueFilter { public PropertyValueMinFilter(string propertyName, object expectedPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, behaviorOnNoMatch) @@ -572,8 +569,7 @@ private static bool ActualValueGreaterThanOrEqualToExpectedValue(string property { try { - var expectedComparable = expectedPropertyValue as IComparable; - if (expectedComparable == null) + if (expectedPropertyValue is not IComparable expectedComparable) { return false; } @@ -587,7 +583,7 @@ private static bool ActualValueGreaterThanOrEqualToExpectedValue(string property } } - internal class PropertyValueMaxFilter : PropertyValueFilter + internal sealed class PropertyValueMaxFilter : PropertyValueFilter { public PropertyValueMaxFilter(string propertyName, object expectedPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, behaviorOnNoMatch) @@ -608,8 +604,7 @@ private static bool ActualValueLessThanOrEqualToExpectedValue(string propertyNam { try { - var actualComparable = actualPropertyValue as IComparable; - if (actualComparable == null) + if (actualPropertyValue is not IComparable actualComparable) { return false; } @@ -626,8 +621,8 @@ private static bool ActualValueLessThanOrEqualToExpectedValue(string propertyNam private int _numberOfResultsFromMi; private int _numberOfMatchingResults; - private readonly List _filters = new List(); - private readonly object _myLock = new object(); + private readonly List _filters = new(); + private readonly object _myLock = new(); #region "Public" interface for client-side filtering @@ -658,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/AddContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs index af8efbd6885..0f9762084d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/AddContentCommand.cs @@ -6,8 +6,6 @@ using System.Management.Automation; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -42,7 +40,7 @@ internal override void SeekContentPosition(List contentHolders) catch (Exception e) // Catch-all OK, 3rd party callout { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderSeekError", SessionStateStrings.ProviderSeekError, holder.PathInfo.Provider, @@ -84,4 +82,3 @@ internal override bool CallShouldProcess(string path) #endregion protected members } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs index 559af13127b..688b6362e16 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs @@ -79,14 +79,13 @@ 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 { var type = typeof(T); - var binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; - T rv = new T(); + const BindingFlags binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; + T rv = new(); using (var instance = session.QueryFirstInstance(nameSpace, CIMHelper.WqlQueryAll(wmiClassName))) { @@ -132,8 +131,7 @@ 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(); @@ -144,11 +142,11 @@ internal static string WqlQueryAll(string from) if (instances != null) { var type = typeof(T); - var binding = BindingFlags.Public | BindingFlags.Instance; + const BindingFlags binding = BindingFlags.Public | BindingFlags.Instance; foreach (var instance in instances) { - T objT = new T(); + T objT = new(); using (instance) { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs index 61726686f28..bd6ba2ee726 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearContentCommand.cs @@ -3,8 +3,6 @@ using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -101,4 +99,3 @@ internal override object GetDynamicParameters(CmdletProviderContext context) } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs index 75ee9a639e3..f63eb8eed91 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearPropertyCommand.cs @@ -4,8 +4,6 @@ using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -86,7 +84,7 @@ public string Name /// internal override object GetDynamicParameters(CmdletProviderContext context) { - Collection propertyCollection = new Collection(); + Collection propertyCollection = new(); propertyCollection.Add(_property); if (Path != null && Path.Length > 0) @@ -126,7 +124,7 @@ protected override void ProcessRecord() CmdletProviderContext currentContext = CmdletProviderContext; currentContext.PassThru = PassThru; - Collection propertyCollection = new Collection(); + Collection propertyCollection = new(); propertyCollection.Add(_property); foreach (string path in Path) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs index 3925c222eef..bc4e44220dd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ClearRecycleBinCommand.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; @@ -42,7 +41,7 @@ public string[] DriveLetter /// /// Property that sets force parameter. This will allow to clear the recyclebin. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -155,7 +154,7 @@ private bool ValidDrivePath(string drivePath) /// /// /// - private bool IsValidPattern(string input) + private static bool IsValidPattern(string input) { return Regex.IsMatch(input, @"^[a-z]{1}$|^[a-z]{1}:$|^[a-z]{1}:\\$", RegexOptions.IgnoreCase); } @@ -166,7 +165,7 @@ private bool IsValidPattern(string input) /// /// /// - private string GetDrivePath(string driveName) + private static string GetDrivePath(string driveName) { string drivePath; if (driveName.EndsWith(":\\", StringComparison.OrdinalIgnoreCase)) @@ -221,7 +220,7 @@ private void EmptyRecycleBin(string drivePath) statusDescription = string.Format(CultureInfo.InvariantCulture, ClearRecycleBinResources.ClearRecycleBinStatusDescriptionByDrive, drivePath); } - ProgressRecord progress = new ProgressRecord(0, activity, statusDescription); + ProgressRecord progress = new(0, activity, statusDescription); progress.PercentComplete = 30; progress.RecordType = ProgressRecordType.Processing; WriteProgress(progress); @@ -238,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 @@ -248,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 38a1b5c48a6..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,18 +19,19 @@ internal static class Clipboard private static string StartProcess( string tool, string args, - string stdin = "") + string stdin = "", + bool readStdout = true) { - ProcessStartInfo startInfo = new ProcessStartInfo(); + ProcessStartInfo startInfo = new(); startInfo.UseShellExecute = false; startInfo.RedirectStandardInput = true; startInfo.RedirectStandardOutput = true; startInfo.RedirectStandardError = true; startInfo.FileName = tool; startInfo.Arguments = args; - string stdout; + string stdout = string.Empty; - using (Process process = new Process()) + using (Process process = new()) { process.StartInfo = startInfo; try @@ -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 bd97d485cd1..79b1c862bfd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs @@ -4,7 +4,6 @@ using System; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Text; using Dbg = System.Management.Automation; @@ -33,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. @@ -51,6 +51,21 @@ public class JoinPathCommand : CoreCommandWithCredentialsBase [Parameter] public SwitchParameter Resolve { get; set; } + /// + /// Gets or sets the extension to use for the resulting path. + /// If not specified, the original extension (if any) is preserved. + /// + /// Behavior: + /// - If the path has an existing extension, it will be replaced with the specified extension. + /// - If the path does not have an extension, the specified extension will be added. + /// - If an empty string is provided, any existing extension will be removed. + /// - A leading dot in the extension is optional; if omitted, one will be added automatically. + /// + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string Extension { get; set; } + #endregion Parameters #region Command code @@ -65,7 +80,15 @@ protected override void ProcessRecord() Path != null, "Since Path is a mandatory parameter, paths should never be null"); - string combinedChildPath = ChildPath; + string combinedChildPath = string.Empty; + + if (this.ChildPath != null) + { + foreach (string childPath in this.ChildPath) + { + combinedChildPath = SessionState.Path.Combine(combinedChildPath, childPath, CmdletProviderContext); + } + } // join the ChildPath elements if (AdditionalChildPath != null) @@ -120,6 +143,12 @@ protected override void ProcessRecord() continue; } + // If Extension parameter is present it is not null due to [ValidateNotNull]. + if (Extension is not null) + { + joinedPath = System.IO.Path.ChangeExtension(joinedPath, Extension.Length == 0 ? null : Extension); + } + if (Resolve) { // Resolve the paths. The default API (GetResolvedPSPathFromPSPath) @@ -219,4 +248,3 @@ protected override void ProcessRecord() } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs index 6f93119c999..94e923bfa6e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs @@ -28,4 +28,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 5d2513a47ae..ea8c111531c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -11,27 +11,19 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation; using System.Management.Automation.Internal; using System.Net; -using System.Reflection; -using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security.Cryptography; -using System.Security.Permissions; using System.Text; using System.Threading; -using Microsoft.Win32; -using Microsoft.PowerShell.Commands.Internal; using Microsoft.Management.Infrastructure; using Microsoft.Management.Infrastructure.Options; -using System.Linq; +using Microsoft.Win32; using Dbg = System.Management.Automation; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "unjoined")] -[module: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "UpTime")] - namespace Microsoft.PowerShell.Commands { #region Restart-Computer @@ -39,18 +31,17 @@ namespace Microsoft.PowerShell.Commands /// /// This exception is thrown when the timeout expires before a computer finishes restarting. /// - [Serializable] public sealed class RestartComputerTimeoutException : RuntimeException { /// /// Name of the computer that is restarting. /// - public string ComputerName { get; private set; } + public string ComputerName { get; } /// /// The timeout value specified by the user. It indicates the seconds to wait before timeout. /// - public int Timeout { get; private set; } + public int Timeout { get; } /// /// Construct a RestartComputerTimeoutException. @@ -91,51 +82,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 - /// - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - 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 } /// @@ -199,9 +145,9 @@ public class RestartComputerCommand : PSCmdlet, IDisposable [Alias("CN", "__SERVER", "Server", "IPAddress")] public string[] ComputerName { get; set; } = new string[] { "." }; - private List _validatedComputerNames = new List(); - private readonly List _waitOnComputers = new List(); - private readonly HashSet _uniqueComputerNames = new HashSet(StringComparer.OrdinalIgnoreCase); + private List _validatedComputerNames = new(); + private readonly List _waitOnComputers = new(); + private readonly HashSet _uniqueComputerNames = new(StringComparer.OrdinalIgnoreCase); /// /// The following is the definition of the input parameter "Credential". @@ -238,7 +184,10 @@ public class RestartComputerCommand : PSCmdlet, IDisposable [ValidateRange(-1, int.MaxValue)] public int Timeout { - get { return _timeout; } + get + { + return _timeout; + } set { @@ -257,7 +206,10 @@ public int Timeout [Parameter(ParameterSetName = DefaultParameterSet)] public WaitForServiceTypes For { - get { return _waitFor; } + get + { + return _waitFor; + } set { @@ -274,10 +226,13 @@ 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; } + get + { + return (short)_delay; + } set { @@ -302,7 +257,7 @@ public Int16 Delay ComputerName = $computerName ScriptBlock = { $true } - SessionOption = NewPSSessionOption -NoMachineProfile + SessionOption = New-PSSessionOption -NoMachineProfile ErrorAction = 'SilentlyContinue' } @@ -319,7 +274,7 @@ public Int16 Delay /// /// The indicator to use when show progress. /// - private string[] _indicator = { "|", "/", "-", "\\" }; + private readonly string[] _indicator = { "|", "/", "-", "\\" }; /// /// The activity id. @@ -341,13 +296,13 @@ public Int16 Delay /// Indicate to exit. /// private bool _exit, _timeUp; - private readonly CancellationTokenSource _cancel = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancel = new(); /// /// A waithandler to wait on. Current thread will wait on it during the delay interval. /// - private readonly ManualResetEventSlim _waitHandler = new ManualResetEventSlim(false); - private readonly Dictionary _computerInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); + private readonly ManualResetEventSlim _waitHandler = new(false); + private readonly Dictionary _computerInfos = new(StringComparer.OrdinalIgnoreCase); // CLR 4.0 Port note - use https://msdn.microsoft.com/library/system.net.networkinformation.ipglobalproperties.hostname(v=vs.110).aspx private readonly string _shortLocalMachineName = Dns.GetHostName(); @@ -389,17 +344,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(); } } @@ -446,7 +394,7 @@ private void ValidateComputerNames() if (!InternalTestHooks.TestWaitStopComputer && Wait && containLocalhost) { // The local machine will be ignored, and an error will be emitted. - InvalidOperationException ex = new InvalidOperationException(ComputerResources.CannotWaitLocalComputer); + InvalidOperationException ex = new(ComputerResources.CannotWaitLocalComputer); WriteError(new ErrorRecord(ex, "CannotWaitLocalComputer", ErrorCategory.InvalidOperation, null)); containLocalhost = false; } @@ -468,7 +416,7 @@ private void ValidateComputerNames() /// private void WriteProgress(string activity, string status, int percent, ProgressRecordType progressRecordType) { - ProgressRecord progress = new ProgressRecord(_activityId, activity, status); + ProgressRecord progress = new(_activityId, activity, status); progress.PercentComplete = percent; progress.RecordType = progressRecordType; WriteProgress(progress); @@ -518,7 +466,7 @@ private void OnTimedEvent(object s) } } - private class ComputerInfo + private sealed class ComputerInfo { internal string LastBootUpTime; internal bool RebootComplete; @@ -536,9 +484,12 @@ private List TestRestartStageUsingWsman(IEnumerable computerName { try { - if (token.IsCancellationRequested) { break; } + if (token.IsCancellationRequested) + { + break; + } - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, token, this)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -594,7 +545,7 @@ private List SetUpComputerInfoUsingWsman(IEnumerable computerNam { try { - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, token, this)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, Credential, WsmanAuthentication, isLocalHost: false, this, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -664,7 +615,7 @@ private void WriteOutTimeoutError(IEnumerable computerNames) #region "Internal Methods" - internal static List TestWmiConnectionUsingWsman(List computerNames, List nextTestList, CancellationToken token, PSCredential credential, string wsmanAuthentication, PSCmdlet cmdlet) + internal static List TestWmiConnectionUsingWsman(List computerNames, List nextTestList, PSCredential credential, string wsmanAuthentication, PSCmdlet cmdlet, CancellationToken token) { // Check if the WMI service "Winmgmt" is started const string wmiServiceQuery = "Select * from " + ComputerWMIHelper.WMI_Class_Service + " Where name = 'Winmgmt'"; @@ -678,9 +629,12 @@ 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, token, cmdlet)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credential, wsmanAuthentication, isLocalHost: false, cmdlet, token)) { bool itemRetrieved = false; IEnumerable mCollection = cimSession.QueryInstances( @@ -730,7 +684,7 @@ internal static List TestWmiConnectionUsingWsman(List computerNa /// internal static List TestPowerShell(List computerNames, List nextTestList, System.Management.Automation.PowerShell powershell, PSCredential credential) { - List psList = new List(); + List psList = new(); try { @@ -788,13 +742,13 @@ protected override void BeginProcessing() // Timeout, For, Delay, Progress cannot be present if Wait is not present if ((_timeoutSpecified || _waitForSpecified || _delaySpecified) && !Wait) { - InvalidOperationException ex = new InvalidOperationException(ComputerResources.RestartComputerInvalidParameter); + InvalidOperationException ex = new(ComputerResources.RestartComputerInvalidParameter); ThrowTerminatingError(new ErrorRecord(ex, "RestartComputerInvalidParameter", ErrorCategory.InvalidOperation, null)); } if (Wait) { - _activityId = (new Random()).Next(); + _activityId = Random.Shared.Next(); if (_timeout == -1 || _timeout >= int.MaxValue / 1000) { _timeoutInMilliseconds = int.MaxValue; @@ -815,8 +769,8 @@ protected override void BeginProcessing() _powershell.AddScript(TestPowershellScript); break; default: - InvalidOperationException ex = new InvalidOperationException(ComputerResources.NoSupportForCombinedServiceType); - ErrorRecord error = new ErrorRecord(ex, "NoSupportForCombinedServiceType", ErrorCategory.InvalidOperation, (int)_waitFor); + InvalidOperationException ex = new(ComputerResources.NoSupportForCombinedServiceType); + ErrorRecord error = new(ex, "NoSupportForCombinedServiceType", ErrorCategory.InvalidOperation, (int)_waitFor); ThrowTerminatingError(error); break; } @@ -833,7 +787,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)) { @@ -908,14 +865,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) @@ -935,7 +896,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) { @@ -951,7 +915,10 @@ protected override void ProcessRecord() } // Test WMI service - if (_exit) { break; } + if (_exit) + { + break; + } if (wmiTestList.Count > 0) { @@ -965,14 +932,20 @@ protected override void ProcessRecord() WriteProgress(_indicator[(indicatorIndex++) % 4] + _activity, _status, _percent, ProgressRecordType.Processing); } - wmiTestList = TestWmiConnectionUsingWsman(wmiTestList, winrmTestList, _cancel.Token, Credential, WsmanAuthentication, this); + wmiTestList = TestWmiConnectionUsingWsman(wmiTestList, winrmTestList, Credential, WsmanAuthentication, this, _cancel.Token); } } - if (isForWmi) { break; } + if (isForWmi) + { + break; + } // Test WinRM service - if (_exit) { break; } + if (_exit) + { + break; + } if (winrmTestList.Count > 0) { @@ -997,16 +970,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) { @@ -1022,7 +1004,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) @@ -1066,18 +1051,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); } @@ -1094,10 +1099,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) { @@ -1122,7 +1124,7 @@ public sealed class StopComputerCommand : PSCmdlet, IDisposable { #region Private Members - private readonly CancellationTokenSource _cancel = new CancellationTokenSource(); + private readonly CancellationTokenSource _cancel = new(); private const int forcedShutdown = 5; // See https://msdn.microsoft.com/library/aa394058(v=vs.85).aspx @@ -1227,7 +1229,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))) { @@ -1272,9 +1277,9 @@ private void ProcessWSManProtocol(object[] flags) /// workgroup computer. Use this command to rename domain workstations and local /// machines only. It cannot be used to rename Domain Controllers. /// - [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 @@ -1394,7 +1399,7 @@ private string ValidateComputerName() { bool isLocalhost = targetComputer.Equals(ComputerWMIHelper.localhostStr, StringComparison.OrdinalIgnoreCase); string errMsg = StringUtil.Format(ComputerResources.InvalidNewName, isLocalhost ? _shortLocalMachineName : targetComputer, NewName); - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new InvalidOperationException(errMsg), "InvalidNewName", ErrorCategory.InvalidArgument, NewName); WriteError(error); @@ -1436,8 +1441,8 @@ private void DoRenameComputerWsman(string computer, string computerName, string try { - using (CancellationTokenSource cancelTokenSource = new CancellationTokenSource()) - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credToUse, WsmanAuthentication, isLocalhost, cancelTokenSource.Token, this)) + using (CancellationTokenSource cancelTokenSource = new()) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(computer, credToUse, WsmanAuthentication, isLocalhost, this, cancelTokenSource.Token)) { var operationOptions = new CimOperationOptions { @@ -1459,7 +1464,7 @@ private void DoRenameComputerWsman(string computer, string computerName, string if (oldName.Equals(newName, StringComparison.OrdinalIgnoreCase)) { string errMsg = StringUtil.Format(ComputerResources.NewNameIsOldName, computerName, newName); - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new InvalidOperationException(errMsg), "NewNameIsOldName", ErrorCategory.InvalidArgument, newName); WriteError(error); @@ -1518,7 +1523,7 @@ private void DoRenameComputerWsman(string computer, string computerName, string { var ex = new Win32Exception(retVal); string errMsg = StringUtil.Format(ComputerResources.FailToRename, computerName, newName, ex.Message); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "FailToRenameComputer", ErrorCategory.OperationStopped, computerName); + ErrorRecord error = new(new InvalidOperationException(errMsg), "FailToRenameComputer", ErrorCategory.OperationStopped, computerName); WriteError(error); } else @@ -1559,14 +1564,14 @@ private void DoRenameComputerWsman(string computer, string computerName, string catch (CimException ex) { string errMsg = StringUtil.Format(ComputerResources.FailToConnectToComputer, computerName, ex.Message); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "RenameComputerException", + ErrorRecord error = new(new InvalidOperationException(errMsg), "RenameComputerException", ErrorCategory.OperationStopped, computerName); WriteError(error); } catch (Exception ex) { string errMsg = StringUtil.Format(ComputerResources.FailToConnectToComputer, computerName, ex.Message); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "RenameComputerException", + ErrorRecord error = new(new InvalidOperationException(errMsg), "RenameComputerException", ErrorCategory.OperationStopped, computerName); WriteError(error); } @@ -1582,7 +1587,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) @@ -1602,7 +1610,10 @@ protected override void ProcessRecord() /// protected override void EndProcessing() { - if (!_containsLocalHost) return; + if (!_containsLocalHost) + { + return; + } DoRenameComputerAction("localhost", _newNameForLocalHost, true); } @@ -1645,7 +1656,7 @@ public override string ToString() /// /// /// - private string FormatLine(string HasSucceeded, string computername) + private static string FormatLine(string HasSucceeded, string computername) { return StringUtil.Format(MatchFormat, HasSucceeded, computername); } @@ -1689,7 +1700,7 @@ public override string ToString() /// /// /// - private string FormatLine(string HasSucceeded, string newcomputername, string oldcomputername) + private static string FormatLine(string HasSucceeded, string newcomputername, string oldcomputername) { return StringUtil.Format(MatchFormat, HasSucceeded, newcomputername, oldcomputername); } @@ -1799,7 +1810,7 @@ internal static string GetLocalAdminUserName(string computerName, PSCredential p string localUserName = null; // The format of local admin username should be "ComputerName\AdminName" - if (psLocalCredential.UserName.Contains("\\")) + if (psLocalCredential.UserName.Contains('\\')) { localUserName = psLocalCredential.UserName; } @@ -1812,7 +1823,7 @@ internal static string GetLocalAdminUserName(string computerName, PSCredential p } else { - localUserName = computerName.Substring(0, dotIndex) + "\\" + psLocalCredential.UserName; + localUserName = string.Concat(computerName.AsSpan(0, dotIndex), "\\", psLocalCredential.UserName); } } @@ -1850,7 +1861,7 @@ internal static string GetRandomPassword(int passwordLength) /// internal static string GetScopeString(string computer, string namespaceParameter) { - StringBuilder returnValue = new StringBuilder("\\\\"); + StringBuilder returnValue = new("\\\\"); if (computer.Equals("::1", StringComparison.OrdinalIgnoreCase) || computer.Equals("[::1]", StringComparison.OrdinalIgnoreCase)) { returnValue.Append("localhost"); @@ -1926,7 +1937,7 @@ internal static string GetMachineNames(string[] computerNames) } string compname = string.Empty; - StringBuilder strComputers = new StringBuilder(); + StringBuilder strComputers = new(); int i = 0; foreach (string computer in computerNames) { @@ -1956,7 +1967,7 @@ internal static string GetMachineNames(string[] computerNames) internal static ComputerChangeInfo GetComputerStatusObject(int errorcode, string computername) { - ComputerChangeInfo computerchangeinfo = new ComputerChangeInfo(); + ComputerChangeInfo computerchangeinfo = new(); computerchangeinfo.ComputerName = computername; if (errorcode != 0) { @@ -1972,7 +1983,7 @@ internal static ComputerChangeInfo GetComputerStatusObject(int errorcode, string internal static RenameComputerChangeInfo GetRenameComputerStatusObject(int errorcode, string newcomputername, string oldcomputername) { - RenameComputerChangeInfo renamecomputerchangeinfo = new RenameComputerChangeInfo(); + RenameComputerChangeInfo renamecomputerchangeinfo = new(); renamecomputerchangeinfo.OldComputerName = oldcomputername; renamecomputerchangeinfo.NewComputerName = newcomputername; if (errorcode != 0) @@ -1989,7 +2000,7 @@ internal static RenameComputerChangeInfo GetRenameComputerStatusObject(int error internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, string computername) { - Win32Exception ex = new Win32Exception(errorcode); + Win32Exception ex = new(errorcode); string additionalmessage = string.Empty; if (ex.NativeErrorCode.Equals(0x00000035)) { @@ -1997,7 +2008,7 @@ internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, st } string message = StringUtil.Format(ComputerResources.OperationFailed, ex.Message, computername, additionalmessage); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, computername); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, computername); cmdlet.WriteError(er); } @@ -2008,54 +2019,24 @@ internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, st /// internal static bool IsComputerNameValid(string computerName) { - bool allDigits = true; + bool hasAsciiLetterOrHyphen = false; if (computerName.Length >= 64) return false; foreach (char t in computerName) { - if (t >= 'A' && t <= 'Z' || - t >= 'a' && t <= 'z') + if (char.IsAsciiLetter(t) || t is '-') { - allDigits = false; - continue; + hasAsciiLetterOrHyphen = true; } - else if (t >= '0' && t <= '9') - { - continue; - } - else if (t == '-') - { - allDigits = false; - continue; - } - else + else if (!char.IsAsciiDigit(t)) { return false; } } - 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; + return hasAsciiLetterOrHyphen; } /// @@ -2107,12 +2088,12 @@ internal static bool InvokeWin32ShutdownUsingWsman( string message = StringUtil.Format(ComputerResources.PrivilegeNotEnabled, computerName, isLocalhost ? ComputerWMIHelper.SE_SHUTDOWN_NAME : ComputerWMIHelper.SE_REMOTE_SHUTDOWN_NAME); - ErrorRecord errorRecord = new ErrorRecord(new InvalidOperationException(message), "PrivilegeNotEnabled", ErrorCategory.InvalidOperation, null); + ErrorRecord errorRecord = new(new InvalidOperationException(message), "PrivilegeNotEnabled", ErrorCategory.InvalidOperation, null); cmdlet.WriteError(errorRecord); return false; } - using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(targetMachine, credInUse, authInUse, isLocalhost, cancelToken, cmdlet)) + using (CimSession cimSession = RemoteDiscoveryHelper.CreateCimSession(targetMachine, credInUse, authInUse, isLocalhost, cmdlet, cancelToken)) { var methodParameters = new CimMethodParametersCollection(); int retVal; @@ -2165,7 +2146,7 @@ internal static bool InvokeWin32ShutdownUsingWsman( { var ex = new Win32Exception(retVal); string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message); - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new InvalidOperationException(errMsg), ErrorFQEID, ErrorCategory.OperationStopped, computerName); cmdlet.WriteError(error); } @@ -2178,14 +2159,14 @@ internal static bool InvokeWin32ShutdownUsingWsman( catch (CimException ex) { string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), ErrorFQEID, + ErrorRecord error = new(new InvalidOperationException(errMsg), ErrorFQEID, ErrorCategory.OperationStopped, computerName); cmdlet.WriteError(error); } catch (Exception ex) { string errMsg = StringUtil.Format(formatErrorMessage, computerName, ex.Message); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), ErrorFQEID, + ErrorRecord error = new(new InvalidOperationException(errMsg), ErrorFQEID, ErrorCategory.OperationStopped, computerName); cmdlet.WriteError(error); } @@ -2227,8 +2208,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 023fc1d0501..22092116330 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,17 +98,34 @@ protected override void BeginProcessing() public class CommandLineCmdletBase : PSCmdlet, IDisposable { #region Private Members - private Process _process = null; + private Process? _process = null; #endregion #region "IDisposable Members" /// - /// Dispose Method. + /// Releases all resources used by the . /// public void Dispose() { - _process?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases the unmanaged resources used by the + /// and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _process?.Dispose(); + } } #endregion "IDisposable Members" @@ -133,24 +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) { - String cmd = ""; + 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", - Arguments = cmd, + 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 b595c0195f9..e7b3ada4ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs @@ -34,7 +34,10 @@ public class ContentCommandBase : CoreCommandWithCredentialsBase, IDisposable [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return Path; } + get + { + return Path; + } set { @@ -108,7 +111,7 @@ public override SwitchParameter Force /// An array of content holder objects that contain the path information /// and content readers/writers for the item represented by the path information. /// - internal List contentStreams = new List(); + internal List contentStreams = new(); /// /// Wraps the content into a PSObject and adds context information as notes. @@ -241,7 +244,7 @@ internal void WriteContentObject(object content, long readCount, PathInfo pathIn /// as they get written to the pipeline. An instance of this cache class is /// only valid for a single path. /// - internal class ContentPathsCache + internal sealed class ContentPathsCache { /// /// Constructs a content cache item. @@ -297,7 +300,7 @@ public PSObject AttachNotes(PSObject content) { // Construct a provider qualified path as the Path note - PSNoteProperty note = new PSNoteProperty("PSPath", PSPath); + PSNoteProperty note = new("PSPath", PSPath); content.Properties.Add(note, true); tracer.WriteLine("Attaching {0} = {1}", "PSPath", PSPath); @@ -336,7 +339,7 @@ public PSObject AttachNotes(PSObject content) /// A struct to hold the path information and the content readers/writers /// for an item. /// - internal struct ContentHolder + internal readonly struct ContentHolder { internal ContentHolder( PathInfo pathInfo, @@ -374,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 { @@ -385,7 +385,7 @@ internal void CloseContent(List contentHolders, bool disposing) // and write out an error. ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentCloseError", SessionStateStrings.ProviderContentCloseError, holder.PathInfo.Provider, @@ -411,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 { @@ -422,7 +419,7 @@ internal void CloseContent(List contentHolders, bool disposing) // and write out an error. ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentCloseError", SessionStateStrings.ProviderContentCloseError, holder.PathInfo.Provider, @@ -480,7 +477,7 @@ internal List GetContentReaders( // Create the results array - List results = new List(); + List results = new(); foreach (PathInfo pathInfo in pathInfos) { @@ -538,7 +535,7 @@ internal List GetContentReaders( if (readers.Count == 1 && readers[0] != null) { ContentHolder holder = - new ContentHolder(pathInfo, readers[0], null); + new(pathInfo, readers[0], null); results.Add(holder); } @@ -573,7 +570,7 @@ internal Collection ResolvePaths( bool allowEmptyResult, CmdletProviderContext currentCommandContext) { - Collection results = new Collection(); + Collection results = new(); foreach (string path in pathsToResolve) { @@ -653,7 +650,7 @@ internal Collection ResolvePaths( out drive); PathInfo pathInfo = - new PathInfo( + new( drive, provider, unresolvedPath, @@ -666,7 +663,7 @@ internal Collection ResolvePaths( { // Detect if the path resolution failed to resolve to a file. string error = StringUtil.Format(NavigationResources.ItemNotFound, Path); - Exception e = new Exception(error); + Exception e = new(error); pathNotFoundErrorRecord = new ErrorRecord( e, @@ -705,14 +702,6 @@ public void Dispose() GC.SuppressFinalize(this); } - /// - /// Finalizer. - /// - ~ContentCommandBase() - { - Dispose(false); - } #endregion IDisposable - } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs index fffb36d2979..3734eb82b0c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -300,7 +300,7 @@ internal void GetCategoryMap() foreach (ShellFolderItem category in catItems) { string path = category.Path; - string catNum = path.Substring(path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase) + 1); + string catNum = path.Substring(path.LastIndexOf('\\') + 1); CategoryMap.Add(catNum, category.Name); } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs index 06add515e1c..33796b23378 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ConvertPathCommand.cs @@ -4,8 +4,6 @@ using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -57,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 @@ -128,4 +136,3 @@ protected override void ProcessRecord() } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs index 699676acfd6..ff13448bc09 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CopyPropertyCommand.cs @@ -3,8 +3,6 @@ using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -36,7 +34,10 @@ public string[] Path [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } set { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs index 45c484744fa..c667116fdd0 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. @@ -1449,4 +1465,3 @@ protected override void BeginProcessing() #endregion RemoveEventLogCommand } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs index 3dcd0d51c4a..c049370e4b4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using Dbg = System.Management.Automation; @@ -123,7 +122,7 @@ public override string[] Exclude /// Gets or sets the recurse switch. /// [Parameter] - [Alias("s")] + [Alias("s", "r")] public SwitchParameter Recurse { get @@ -365,4 +364,3 @@ protected override void ProcessRecord() #endregion command code } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs index dc272f40281..a2b7f03ce70 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Management.Automation; +using System.Management.Automation.Language; using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands @@ -23,7 +25,10 @@ public class GetClipboardCommand : PSCmdlet [Parameter] public SwitchParameter Raw { - get { return _raw; } + get + { + return _raw; + } set { @@ -31,6 +36,13 @@ public SwitchParameter Raw } } + /// + /// Gets or sets the delimiters to use when splitting the clipboard content. + /// + [Parameter] + [ArgumentCompleter(typeof(DelimiterCompleter))] + public string[] Delimiter { get; set; } = [Environment.NewLine]; + private bool _raw; /// @@ -65,11 +77,40 @@ private List GetClipboardContentAsText() } else { - string[] splitSymbol = { Environment.NewLine }; - result.AddRange(textContent.Split(splitSymbol, StringSplitOptions.None)); + result.AddRange(textContent.Split(Delimiter, StringSplitOptions.None)); } return result; } } + + /// + /// Provides argument completion for the Delimiter parameter. + /// + public sealed class DelimiterCompleter : IArgumentCompleter + { + /// + /// Provides argument completion for the Delimiter parameter. + /// + /// The name of the command that is being completed. + /// The name of the parameter that is being completed. + /// The input text to filter the results by. + /// The ast of the command that triggered the completion. + /// The parameters bound to the command. + /// Completion results. + public IEnumerable CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + { + wordToComplete ??= string.Empty; + var pattern = new WildcardPattern(wordToComplete + '*', WildcardOptions.IgnoreCase); + if (pattern.IsMatch("CRLF") || pattern.IsMatch("Windows")) + { + yield return new CompletionResult("\"`r`n\"", "CRLF", CompletionResultType.ParameterValue, "Windows (CRLF)"); + } + + if (pattern.IsMatch("LF") || pattern.IsMatch("Unix") || pattern.IsMatch("Linux")) + { + yield return new CompletionResult("\"`n\"", "LF", CompletionResultType.ParameterValue, "UNIX (LF)"); + } + } + } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index d73d191585e..87f96c436ea 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; @@ -85,7 +85,7 @@ private class MiscInfoGroup #endregion Static Data and Constants #region Instance Data - private string _machineName = localMachineName; // we might need to have cmdlet work on another machine + private readonly string _machineName = localMachineName; // we might need to have cmdlet work on another machine /// /// Collection of property names from the Property parameter, @@ -225,7 +225,7 @@ protected override void ProcessRecord() /// private void UpdateProgress(string status) { - ProgressRecord progress = new ProgressRecord(0, activity, status ?? ComputerResources.ProgressStatusCompleted); + ProgressRecord progress = new(0, activity, status ?? ComputerResources.ProgressStatusCompleted); progress.RecordType = status == null ? ProgressRecordType.Completed : ProgressRecordType.Processing; WriteProgress(progress); @@ -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) @@ -486,7 +486,7 @@ private static DeviceGuardInfo GetDeviceGuard(CimSession session) /// private static HyperVInfo GetHyperVisorInfo(CimSession session) { - HyperVInfo info = new HyperVInfo(); + HyperVInfo info = new(); bool ok = false; CimInstance 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) { @@ -1142,7 +1139,7 @@ internal static string GetLocaleName(string locale) } } - return culture == null ? null : culture.Name; + return culture?.Name; } /// @@ -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)) { @@ -1335,9 +1332,10 @@ protected static string GetLanguageName(uint? lcid) return null; } } + #pragma warning disable 649 // fields and properties in these class are assigned dynamically [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBaseBoard + internal sealed class WmiBaseBoard { public string Caption; public string[] ConfigOptions; @@ -1370,9 +1368,9 @@ internal class WmiBaseBoard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBios : WmiClassBase + internal sealed class WmiBios : WmiClassBase { - public UInt16[] BiosCharacteristics; + public ushort[] BiosCharacteristics; public string[] BIOSVersion; public string BuildNumber; public string Caption; @@ -1405,7 +1403,7 @@ internal class WmiBios : WmiClassBase } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiComputerSystem + internal sealed class WmiComputerSystem { public ushort? AdminPasswordStatus; public bool? AutomaticManagedPagefile; @@ -1415,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; @@ -1441,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; @@ -1452,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; @@ -1488,14 +1486,14 @@ public PowerManagementCapabilities[] GetPowerManagementCapabilities() } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiDeviceGuard + internal sealed class WmiDeviceGuard { - public 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 @@ -1563,7 +1561,7 @@ public DeviceGuard AsOutputType } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiKeyboard + internal sealed class WmiKeyboard { public ushort? Availability; public string Caption; @@ -1581,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; @@ -1590,14 +1588,14 @@ internal class WmiKeyboard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WMiLogicalMemory + internal sealed class WMiLogicalMemory { // TODO: fill this in!!! public uint? TotalPhysicalMemory; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiMsftNetAdapter + internal sealed class WmiMsftNetAdapter { public string Caption; public string Description; @@ -1612,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; @@ -1679,13 +1677,13 @@ 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; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapter + internal sealed class WmiNetworkAdapter { public string AdapterType; public ushort? AdapterTypeID; @@ -1716,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; @@ -1729,7 +1727,7 @@ internal class WmiNetworkAdapter } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapterConfiguration + internal sealed class WmiNetworkAdapterConfiguration { public bool? ArpAlwaysSourceRoute; public bool? ArpUseEtherSNAP; @@ -1752,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; @@ -1768,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; @@ -1795,7 +1793,7 @@ internal class WmiNetworkAdapterConfiguration } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiOperatingSystem : WmiClassBase + internal sealed class WmiOperatingSystem : WmiClassBase { #region Fields public string BootDevice; @@ -1806,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; @@ -1884,7 +1882,7 @@ public string GetLocale() #endregion Public Methods #region Private Methods - private OSProductSuite[] MakeProductSuites(uint? suiteMask) + private static OSProductSuite[] MakeProductSuites(uint? suiteMask) { if (suiteMask == null) return null; @@ -1892,8 +1890,8 @@ private 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(); @@ -1902,7 +1900,7 @@ private OSProductSuite[] MakeProductSuites(uint? suiteMask) } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiPageFileUsage + internal sealed class WmiPageFileUsage { public uint? AllocatedBaseSize; public string Caption; @@ -1916,7 +1914,7 @@ internal class WmiPageFileUsage } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiProcessor + internal sealed class WmiProcessor { public ushort? AddressWidth; public ushort? Architecture; @@ -1953,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; @@ -1979,7 +1977,7 @@ internal class WmiProcessor #endregion Intermediate WMI classes #region Other Intermediate classes - internal class RegWinNtCurrentVersion + internal sealed class RegWinNtCurrentVersion { public string BuildLabEx; public string CurrentVersion; @@ -2281,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 @@ -2319,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. @@ -2495,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. @@ -2523,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. @@ -2674,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. @@ -2742,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 @@ -2908,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. @@ -3057,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; } @@ -3327,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, /// @@ -3686,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 } /// @@ -5085,7 +5098,7 @@ public enum SoftwareElementState #endregion Output components #region Native - internal static class Native + internal static partial class Native { private static class PInvokeDllNames { @@ -5098,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. @@ -5125,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 @@ -5136,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 86f252fb8fc..a9d8772a5fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetContentCommand.cs @@ -31,45 +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 - { - set - { - _backCount = value; - _tailSpecified = true; - } - - get { return _backCount; } - } - - 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 @@ -95,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 /// @@ -113,10 +79,10 @@ 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 ErrorRecord(new InvalidOperationException(errMsg), "TailAndHeadCannotCoexist", ErrorCategory.InvalidOperation, null); + ErrorRecord error = new(new InvalidOperationException(errMsg), "TailAndHeadCannotCoexist", ErrorCategory.InvalidOperation, null); WriteError(error); return; } @@ -138,19 +104,17 @@ 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 FileSystemContentReaderWriter)) + if (Tail != -1 && holder.Reader is not FileSystemContentReaderWriter) { string errMsg = SessionStateStrings.GetContent_TailNotSupported; - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "TailNotSupported", ErrorCategory.InvalidOperation, Tail); + ErrorRecord error = new(new InvalidOperationException(errMsg), "TailNotSupported", ErrorCategory.InvalidOperation, Tail); WriteError(error); 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. @@ -165,7 +129,7 @@ protected override void ProcessRecord() catch (Exception e) { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentReadError", SessionStateStrings.ProviderContentReadError, holder.PathInfo.Provider, @@ -194,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 ProviderInvocationException( - "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); @@ -274,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(ContentHolder holder, CmdletProviderContext currentContext) + 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; @@ -294,7 +247,7 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur catch (Exception e) { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentReadError", SessionStateStrings.ProviderContentReadError, holder.PathInfo.Provider, @@ -324,7 +277,10 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur foreach (object entry in results) { if (tailResultQueue.Count == Tail) + { tailResultQueue.Dequeue(); + } + tailResultQueue.Enqueue(entry); } } @@ -346,21 +302,25 @@ private bool ScanForwardsForTail(ContentHolder holder, CmdletProviderContext cur { // 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); @@ -416,4 +376,3 @@ protected override void EndProcessing() } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs index 7f6e4c5b1b4..7523a497390 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetPropertyCommand.cs @@ -5,8 +5,6 @@ using System.Diagnostics.CodeAnalysis; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs index 484ddb96680..6b6551619f1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs @@ -23,4 +23,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs index f460b2c4957..27f812c7a80 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(); } @@ -438,4 +438,3 @@ private string GetClassNameFromQuery(string query) #endregion Command code } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs index c0404fa133c..d0f15346396 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs @@ -66,7 +66,7 @@ public sealed class GetHotFixCommand : PSCmdlet, IDisposable private ManagementObjectSearcher _searchProcess; private bool _inputContainsWildcard = false; - private readonly ConnectionOptions _connectionOptions = new ConnectionOptions(); + private readonly ConnectionOptions _connectionOptions = new(); /// /// Sets connection options. @@ -87,8 +87,8 @@ protected override void ProcessRecord() foreach (string computer in ComputerName) { bool foundRecord = false; - StringBuilder queryString = new StringBuilder(); - ManagementScope scope = new ManagementScope(ComputerWMIHelper.GetScopeString(computer, ComputerWMIHelper.WMI_Path_CIM), _connectionOptions); + StringBuilder queryString = new(); + ManagementScope scope = new(ComputerWMIHelper.GetScopeString(computer, ComputerWMIHelper.WMI_Path_CIM), _connectionOptions); scope.Connect(); if (Id != null) { @@ -97,14 +97,14 @@ protected override void ProcessRecord() { queryString.Append("HotFixID= '"); queryString.Append(Id[i].Replace("'", "\\'")); - queryString.Append("'"); + queryString.Append('\''); if (i < Id.Length - 1) { queryString.Append(" Or "); } } - queryString.Append(")"); + queryString.Append(')'); } else { @@ -134,7 +134,7 @@ protected override void ProcessRecord() { try { - SecurityIdentifier secObj = new SecurityIdentifier(installed); + SecurityIdentifier secObj = new(installed); obj["InstalledBy"] = secObj.Translate(typeof(NTAccount)); } catch (IdentityNotMappedException) @@ -169,10 +169,7 @@ protected override void ProcessRecord() /// protected override void StopProcessing() { - if (_searchProcess != null) - { - _searchProcess.Dispose(); - } + _searchProcess?.Dispose(); } #endregion Overrides @@ -209,29 +206,11 @@ private bool FilterMatch(ManagementObject obj) #region "IDisposable Members" /// - /// Dispose Method. + /// Release all resources. /// public void Dispose() { - this.Dispose(true); - // Use SuppressFinalize in case a subclass - // of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - /// - /// Dispose Method. - /// - /// - public void Dispose(bool disposing) - { - if (disposing) - { - if (_searchProcess != null) - { - _searchProcess.Dispose(); - } - } + _searchProcess?.Dispose(); } #endregion "IDisposable Members" diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/JobProcessCollection.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/JobProcessCollection.cs new file mode 100644 index 00000000000..78560543939 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/JobProcessCollection.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable +#if !UNIX +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Microsoft.PowerShell.Commands; + +/// +/// 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 0fcc8a3a244..a40cae64b9f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/MovePropertyCommand.cs @@ -4,8 +4,6 @@ using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -37,7 +35,10 @@ public string[] Path [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } set { @@ -53,14 +54,14 @@ public string[] LiteralPath [Alias("PSProperty")] public string[] Name { - get { return _property; } + get + { + return _property; + } 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 4389f657f69..6ab8f1d652d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -25,7 +25,7 @@ public abstract class CoreCommandBase : PSCmdlet, IDynamicParameters /// An instance of the PSTraceSource class used for trace output /// using "NavigationCommands" as the category. /// - [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 @@ -39,7 +39,7 @@ internal virtual CmdletProviderContext CmdletProviderContext { get { - CmdletProviderContext coreCommandContext = new CmdletProviderContext(this); + CmdletProviderContext coreCommandContext = new(this); coreCommandContext.Force = Force; @@ -107,7 +107,7 @@ protected bool DoesProviderSupportShouldProcess(string[] paths) // may be getting piped in. bool result = true; - if (paths != null && paths.Length >= 0) + if (paths != null) { foreach (string path in paths) { @@ -165,7 +165,7 @@ protected override void StopProcessing() } internal Collection stopContextCollection = - new Collection(); + new(); /// /// Gets or sets the filter property. @@ -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 @@ -301,7 +301,7 @@ internal override CmdletProviderContext CmdletProviderContext { get { - CmdletProviderContext coreCommandContext = new CmdletProviderContext(this, Credential); + CmdletProviderContext coreCommandContext = new(this, Credential); coreCommandContext.Force = Force; Collection includeFilter = @@ -447,7 +447,7 @@ protected override void ProcessRecord() catch (DriveNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -458,7 +458,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingProvider", ErrorCategory.ObjectNotFound, @@ -469,7 +469,7 @@ protected override void ProcessRecord() catch (ArgumentException argException) { ErrorRecord errorRecord = - new ErrorRecord( + new( argException, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -523,7 +523,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException e) { ErrorRecord errorRecord = - new ErrorRecord( + new( e, "GetLocationNoMatchingProvider", ErrorCategory.ObjectNotFound, @@ -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; } } @@ -654,7 +654,7 @@ public string Path } /// - /// Gets or sets the path path property, when bound from the pipeline. + /// Gets or sets the path property, when bound from the pipeline. /// [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)] @@ -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 @@ -1237,7 +1238,7 @@ protected override void ProcessRecord() // -Persist switch parameter is supported only for FileSystem provider. if (Persist && !provider.Name.Equals(FileSystemProvider.ProviderName, StringComparison.OrdinalIgnoreCase)) { - ErrorRecord er = new ErrorRecord(new NotSupportedException(FileSystemProviderStrings.PersistNotSupported), "DriveRootNotNetworkPath", ErrorCategory.InvalidArgument, this); + ErrorRecord er = new(new NotSupportedException(FileSystemProviderStrings.PersistNotSupported), "DriveRootNotNetworkPath", ErrorCategory.InvalidArgument, this); ThrowTerminatingError(er); } @@ -1249,7 +1250,7 @@ protected override void ProcessRecord() // Create the new drive PSDriveInfo newDrive = - new PSDriveInfo( + new( Name, provider, Root, @@ -1368,7 +1369,7 @@ internal List GetMatchingDrives( string[] providerNames, string scope) { - List results = new List(); + List results = new(); if (providerNames == null || providerNames.Length == 0) { @@ -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; } /// @@ -1609,8 +1611,7 @@ protected override void ProcessRecord() if (!Force && drive == SessionState.Drive.Current) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RemoveDriveInUse, drive.Name); @@ -1637,7 +1638,7 @@ protected override void ProcessRecord() if (verifyMatch && !foundMatch) { - DriveNotFoundException e = new DriveNotFoundException( + DriveNotFoundException e = new( driveName, "DriveNotFound", SessionStateStrings.DriveNotFound); @@ -1654,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")] @@ -1702,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; } /// @@ -1776,7 +1778,7 @@ protected override void ProcessRecord() if (!WildcardPattern.ContainsWildcardCharacters(driveName)) { DriveNotFoundException driveNotFound = - new DriveNotFoundException( + new( driveName, "DriveNotFound", SessionStateStrings.DriveNotFound); @@ -1793,7 +1795,7 @@ protected override void ProcessRecord() catch (DriveNotFoundException driveNotFound) { ErrorRecord errorRecord = - new ErrorRecord( + new( driveNotFound, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -1803,7 +1805,7 @@ protected override void ProcessRecord() catch (ProviderNotFoundException providerNotFound) { ErrorRecord errorRecord = - new ErrorRecord( + new( providerNotFound, "GetLocationNoMatchingDrive", ErrorCategory.ObjectNotFound, @@ -2536,10 +2538,10 @@ protected override void ProcessRecord() try { resolvedPSPaths = SessionState.Path.GetResolvedPSPathFromPSPath(path, currentContext); - if (true == SuppressWildcardExpansion && 0 == resolvedPSPaths.Count) + if (SuppressWildcardExpansion == true && resolvedPSPaths.Count == 0) { ItemNotFoundException pathNotFound = - new ItemNotFoundException( + new( path, "PathNotFound", SessionStateStrings.PathNotFound); @@ -2637,8 +2639,7 @@ protected override void ProcessRecord() if (isCurrentLocationOrAncestor) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RemoveItemInUse, resolvedPath.Path); @@ -2700,8 +2701,8 @@ protected override void ProcessRecord() { try { - System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(providerPath); - if (di != null && (di.Attributes & System.IO.FileAttributes.ReparsePoint) != 0) + System.IO.DirectoryInfo di = new(providerPath); + if (InternalSymbolicLinkLinkCodeMethods.IsReparsePointLikeSymlink(di)) { shouldRecurse = false; treatAsFile = true; @@ -2717,7 +2718,7 @@ protected override void ProcessRecord() { // Get the localized prompt string - string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, resolvedPath.Path); + string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, providerPath); // Confirm the user wants to remove all children and the item even if // they did not specify -recurse @@ -2941,7 +2942,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context) private Collection GetResolvedPaths(string path) { - Collection results = new Collection(); + Collection results = new(); try { results = SessionState.Path.GetResolvedPSPathFromPSPath(path, CmdletProviderContext); @@ -3012,8 +3013,7 @@ private void MoveItem(string path, bool literalPath = false) if (!InvokeProvider.Item.Exists(path, currentContext)) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.MoveItemDoesntExist, path); @@ -3099,8 +3099,7 @@ private void MoveItem(string path, bool literalPath = false) if (isCurrentLocationOrAncestor) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.MoveItemInUse, path); @@ -3354,8 +3353,7 @@ private void RenameItem(string path, bool literalPath = false) if (!InvokeProvider.Item.Exists(path, currentContext)) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RenameItemDoesntExist, path); @@ -3441,8 +3439,7 @@ private void RenameItem(string path, bool literalPath = false) if (isCurrentLocationOrAncestor) { PSInvalidOperationException invalidOperation = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( NavigationResources.RenamedItemInUse, path); @@ -4134,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 e3982d49034..fb134ce7a11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs @@ -1,9 +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 Dbg = System.Management.Automation; +using System.Management.Automation.Language; namespace Microsoft.PowerShell.Commands { @@ -65,6 +67,9 @@ public string[] LiteralPath /// [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] +#if !UNIX + [ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))] +#endif public string PropertyType { get; set; } /// @@ -177,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 a1dd7895fc1..ca616301ebb 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; } @@ -184,7 +184,7 @@ public string[] LiteralPath /// protected override void ProcessRecord() { - StringCollection pathsToParse = new StringCollection(); + StringCollection pathsToParse = new(); if (Resolve) { @@ -289,137 +289,125 @@ protected override void ProcessRecord() { string result = null; - switch (ParameterSetName) + // Check switch parameters in order of specificity + if (IsAbsolute) { - case isAbsoluteSet: - string ignored; - bool isPathAbsolute = - SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); + string ignored; + bool isPathAbsolute = + SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); - WriteObject(isPathAbsolute); - continue; + WriteObject(isPathAbsolute); + continue; + } + else if (Qualifier) + { + int separatorIndex = pathsToParse[index].IndexOf(':'); - case qualifierSet: - int separatorIndex = pathsToParse[index].IndexOf(':'); + if (separatorIndex < 0) + { + FormatException e = + new( + StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); + WriteError( + new ErrorRecord( + e, + "ParsePathFormatError", // RENAME + ErrorCategory.InvalidArgument, + pathsToParse[index])); + continue; + } + else + { + // Check to see if it is provider or drive qualified - if (separatorIndex < 0) - { - FormatException e = - new FormatException( - StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); - WriteError( - new ErrorRecord( - e, - "ParsePathFormatError", // RENAME - ErrorCategory.InvalidArgument, - pathsToParse[index])); - continue; - } - else + if (SessionState.Path.IsProviderQualified(pathsToParse[index])) { - // Check to see if it is provider or drive qualified - - if (SessionState.Path.IsProviderQualified(pathsToParse[index])) - { - // The plus 2 is for the length of the provider separator - // which is "::" - - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 2); - } - else - { - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 1); - } - } + // The plus 2 is for the length of the provider separator + // which is "::" - break; - - case parentSet: - case literalPathSet: - try - { result = - SessionState.Path.ParseParent( - pathsToParse[index], - string.Empty, - CmdletProviderContext, - true); + pathsToParse[index].Substring( + 0, + separatorIndex + 2); } - catch (PSNotSupportedException) - { - // Since getting the parent path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the parent - // is asking for an empty string. - result = string.Empty; - } - - break; - - case leafSet: - case leafBaseSet: - case extensionSet: - try + else { - // default handles leafSet result = - SessionState.Path.ParseChildName( - pathsToParse[index], - CmdletProviderContext, - true); - if (LeafBase) - { - result = System.IO.Path.GetFileNameWithoutExtension(result); - } - else if (Extension) - { - result = System.IO.Path.GetExtension(result); - } + pathsToParse[index].Substring( + 0, + separatorIndex + 1); } - catch (PSNotSupportedException) + } + } + else if (Leaf || LeafBase || Extension) + { + try + { + result = + SessionState.Path.ParseChildName( + pathsToParse[index], + CmdletProviderContext, + true); + if (LeafBase) { - // Since getting the leaf part of a path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the leaf - // is asking for the specified path back. - result = pathsToParse[index]; + result = System.IO.Path.GetFileNameWithoutExtension(result); } - catch (DriveNotFoundException driveNotFound) + else if (Extension) { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; + result = System.IO.Path.GetExtension(result); } - - break; - - case noQualifierSet: - result = RemoveQualifier(pathsToParse[index]); - break; - - default: - Dbg.Diagnostics.Assert( - false, - "Only a known parameter set should be called"); - break; + } + catch (PSNotSupportedException) + { + // Since getting the leaf part of a path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the leaf + // is asking for the specified path back. + result = pathsToParse[index]; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + continue; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + continue; + } + } + else if (NoQualifier) + { + result = RemoveQualifier(pathsToParse[index]); + } + else + { + // None of the switch parameters are true: default to -Parent behavior + try + { + result = + SessionState.Path.ParseParent( + pathsToParse[index], + string.Empty, + CmdletProviderContext, + true); + } + catch (PSNotSupportedException) + { + // Since getting the parent path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the parent + // is asking for an empty string. + result = string.Empty; + } } if (result != null) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs index 83ce50d0049..19bad39785b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughContentCommandBase.cs @@ -3,8 +3,6 @@ using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -76,4 +74,3 @@ internal CmdletProviderContext GetCurrentContext() #endregion protected members } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs index f2d18660ee5..8b76ac36786 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PassThroughPropertyCommandBase.cs @@ -3,8 +3,6 @@ using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -102,4 +100,3 @@ internal CmdletProviderContext GetCurrentContext() #endregion protected members } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index c20ef98a80d..91efda12263 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,23 +12,17 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Language; using System.Net; -using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; -using System.Security.Permissions; using System.Security.Principal; using System.Text; using System.Threading; - using Microsoft.Management.Infrastructure; using Microsoft.PowerShell.Commands.Internal; using Microsoft.Win32.SafeHandles; -using FileNakedHandle = System.IntPtr; -using DWORD = System.UInt32; - namespace Microsoft.PowerShell.Commands { #region ProcessBaseCommand @@ -58,7 +53,7 @@ internal enum MatchMode /// Select the processes specified as input. /// ByInput - }; + } /// /// The current process selection mode. /// @@ -107,8 +102,8 @@ public virtual Process[] InputObject // We use a Dictionary to optimize the check whether the object // is already in the list. - private List _matchingProcesses = new List(); - private Dictionary _keys = new Dictionary(); + private List _matchingProcesses = new(); + private readonly Dictionary _keys = new(); /// /// Retrieve the list of all processes matching the Name, Id @@ -153,7 +148,7 @@ private static int ProcessComparison(Process x, Process y) SafeGetProcessName(x), SafeGetProcessName(y), StringComparison.OrdinalIgnoreCase); - if (0 != diff) + if (diff != 0) return diff; return SafeGetProcessId(x) - SafeGetProcessId(y); } @@ -237,7 +232,7 @@ private void RetrieveMatchingProcessesById() catch (ArgumentException) { WriteNonTerminatingError( - "", + string.Empty, processId, processId, null, @@ -270,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 List(); - processes.AddRange(Process.GetProcesses()); - _allProcesses = processes.ToArray(); - } + internal Process[] AllProcesses => _allProcesses ??= Process.GetProcesses(); - return _allProcesses; - } - } - - private Process[] _allProcesses = null; + private Process[] _allProcesses; /// /// Add to , @@ -362,7 +344,7 @@ internal void WriteNonTerminatingError( processId, (innerException == null) ? string.Empty : innerException.Message); ProcessCommandException exception = - new ProcessCommandException(message, innerException); + new(message, innerException); exception.ProcessName = processName; WriteError(new ErrorRecord( @@ -480,7 +462,10 @@ public sealed class GetProcessCommand : ProcessBaseCommand [ValidateNotNullOrEmpty] public string[] Name { - get { return processNames; } + get + { + return processNames; + } set { @@ -533,28 +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; } + public SwitchParameter IncludeUserName { get; set; } - set { _includeUserName = value; } - } - - private bool _includeUserName = false; - - /// + /// /// 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)] @@ -566,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. /// @@ -655,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); @@ -664,7 +631,7 @@ protected override void ProcessRecord() { try { - ProcessModule mainModule = PsUtils.GetMainModule(process); + ProcessModule mainModule = process.MainModule; if (mainModule != null) { WriteObject(mainModule.FileVersionInfo, true); @@ -684,7 +651,7 @@ protected override void ProcessRecord() { if (exception.HResult == 299) { - WriteObject(PsUtils.GetMainModule(process).FileVersionInfo, true); + WriteObject(process.MainModule?.FileVersionInfo, true); } else { @@ -703,7 +670,7 @@ protected override void ProcessRecord() } else { - WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : (object)process); + WriteObject(IncludeUserName.IsPresent ? AddUserNameToProcess(process) : process); } } } @@ -728,7 +695,7 @@ private static PSObject AddUserNameToProcess(Process process) string userName = RetrieveProcessUserName(process); PSObject processAsPsobj = PSObject.AsPSObject(process); - PSNoteProperty noteProperty = new PSNoteProperty("UserName", userName); + PSNoteProperty noteProperty = new("UserName", userName); processAsPsobj.Properties.Add(noteProperty, true); processAsPsobj.TypeNames.Insert(0, TypeNameForProcessWithUserName); @@ -754,54 +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; - Span userNameStr = stackalloc char[userNameLength]; - Span domainNameStr = stackalloc char[domainNameLength]; - 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 @@ -881,7 +840,10 @@ public int[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return processNames; } + get + { + return processNames; + } set { @@ -911,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; @@ -924,7 +898,7 @@ public int Timeout /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { if (_waitHandle != null) { @@ -942,12 +916,9 @@ public void Dispose() // Handle Exited event and display process information. private void myProcess_Exited(object sender, System.EventArgs e) { - if (0 == System.Threading.Interlocked.Decrement(ref _numberOfProcessesToWaitFor)) + if (Any || (Interlocked.Decrement(ref _numberOfProcessesToWaitFor) == 0)) { - if (_waitHandle != null) - { - _waitHandle.Set(); - } + _waitHandle?.Set(); } } @@ -955,7 +926,7 @@ private void myProcess_Exited(object sender, System.EventArgs e) #region Overrides - private List _processList = new List(); + private readonly List _processList = new(); // Wait handle which is used by thread to sleep. private ManualResetEvent _waitHandle; @@ -977,7 +948,7 @@ protected override void ProcessRecord() } // It cannot wait on itself - if (process.Id.Equals(System.Diagnostics.Process.GetCurrentProcess().Id)) + if (process.Id.Equals(Environment.ProcessId)) { WriteNonTerminatingError(process, null, ProcessResources.WaitOnItself, "WaitOnItself", ErrorCategory.ObjectNotFound); continue; @@ -997,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; @@ -1013,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 { @@ -1025,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 ErrorRecord(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 } @@ -1185,7 +1165,10 @@ protected override void ProcessRecord() SafeGetProcessName(process), SafeGetProcessId(process)); - if (!ShouldProcess(targetString)) { continue; } + if (!ShouldProcess(targetString)) + { + continue; + } try { @@ -1217,7 +1200,7 @@ protected override void ProcessRecord() try { - if (Process.GetCurrentProcess().Id == SafeGetProcessId(process)) + if (Environment.ProcessId == SafeGetProcessId(process)) { _shouldKillCurrentProcess = true; continue; @@ -1343,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; @@ -1390,9 +1376,9 @@ private void StopDependentService(Process process) /// /// Stops the given process throws non terminating error if can't. - /// Process to be stopped. - /// True if process stopped successfully else false. /// + /// Process to be stopped. + /// True if process stopped successfully else false. private void StopProcess(Process process) { Exception exception = null; @@ -1475,7 +1461,10 @@ public int[] Id [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] Name { - get { return processNames; } + get + { + return processNames; + } set { @@ -1500,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) @@ -1515,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) { @@ -1566,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)), @@ -1581,18 +1575,19 @@ private void AttachDebuggerToProcess(Process process) /// /// Map the return code from 'AttachDebugger' to error message. /// - private string MapReturnCodeToErrorMessage(int returnCode) + 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; } @@ -1608,7 +1603,7 @@ private 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 @@ -1638,7 +1633,10 @@ public sealed class StartProcessCommand : PSCmdlet, IDisposable [Credential] public PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -1663,7 +1661,10 @@ public PSCredential Credential [Alias("Lup")] public SwitchParameter LoadUserProfile { - get { return _loaduserprofile; } + get + { + return _loaduserprofile; + } set { @@ -1675,13 +1676,16 @@ 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")] public SwitchParameter NoNewWindow { - get { return _nonewwindow; } + get + { + return _nonewwindow; + } set { @@ -1706,7 +1710,10 @@ public SwitchParameter NoNewWindow [ValidateNotNullOrEmpty] public string RedirectStandardError { - get { return _redirectstandarderror; } + get + { + return _redirectstandarderror; + } set { @@ -1725,7 +1732,10 @@ public string RedirectStandardError [ValidateNotNullOrEmpty] public string RedirectStandardInput { - get { return _redirectstandardinput; } + get + { + return _redirectstandardinput; + } set { @@ -1744,7 +1754,10 @@ public string RedirectStandardInput [ValidateNotNullOrEmpty] public string RedirectStandardOutput { - get { return _redirectstandardoutput; } + get + { + return _redirectstandardoutput; + } set { @@ -1763,6 +1776,7 @@ public string RedirectStandardOutput /// [Parameter(ParameterSetName = "UseShellExecute")] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(VerbArgumentCompleter))] public string Verb { get; set; } /// @@ -1772,7 +1786,10 @@ public string RedirectStandardOutput [ValidateNotNullOrEmpty] public ProcessWindowStyle WindowStyle { - get { return _windowstyle; } + get + { + return _windowstyle; + } set { @@ -1796,7 +1813,10 @@ public ProcessWindowStyle WindowStyle [Parameter(ParameterSetName = "Default")] public SwitchParameter UseNewEnvironment { - get { return _UseNewEnvironment; } + get + { + return _UseNewEnvironment; + } set { @@ -1807,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 @@ -1825,7 +1865,7 @@ protected override void BeginProcessing() if (_nonewwindow && _windowstyleSpecified) { message = StringUtil.Format(ProcessResources.ContradictParametersSpecified, "-NoNewWindow", "-WindowStyle"); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1843,12 +1883,12 @@ protected override void BeginProcessing() if (!string.IsNullOrEmpty(message)) { - ErrorRecord er = new ErrorRecord(new NotSupportedException(message), "NotSupportedException", ErrorCategory.NotImplemented, null); + ErrorRecord er = new(new NotSupportedException(message), "NotSupportedException", ErrorCategory.NotImplemented, null); ThrowTerminatingError(er); } } - ProcessStartInfo startInfo = new ProcessStartInfo(); + ProcessStartInfo startInfo = new(); // Use ShellExecute by default if we are running on full windows SKUs startInfo.UseShellExecute = Platform.IsWindowsDesktop; @@ -1863,6 +1903,7 @@ protected override void BeginProcessing() } catch (CommandNotFoundException) { + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path they are specifying and the process is on the user's system except for remoting in which case restricted remoting security guidelines should be used. startInfo.FileName = FilePath; #if UNIX // Arguments are passed incorrectly to the executable used for ShellExecute and not to filename https://github.com/dotnet/corefx/issues/30718 @@ -1886,7 +1927,7 @@ protected override void BeginProcessing() if (!Directory.Exists(WorkingDirectory)) { message = StringUtil.Format(ProcessResources.InvalidInput, "WorkingDirectory"); - ErrorRecord er = new ErrorRecord(new DirectoryNotFoundException(message), "DirectoryNotFoundException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new DirectoryNotFoundException(message), "DirectoryNotFoundException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1895,8 +1936,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")) @@ -1909,13 +1954,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; } @@ -1945,7 +1997,7 @@ protected override void BeginProcessing() if (!File.Exists(_redirectstandardinput)) { message = StringUtil.Format(ProcessResources.InvalidInput, "RedirectStandardInput '" + this.RedirectStandardInput + "'"); - ErrorRecord er = new ErrorRecord(new FileNotFoundException(message), "FileNotFoundException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new FileNotFoundException(message), "FileNotFoundException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1959,7 +2011,7 @@ protected override void BeginProcessing() if (_redirectstandardinput.Equals(_redirectstandardoutput, StringComparison.OrdinalIgnoreCase)) { message = StringUtil.Format(ProcessResources.DuplicateEntry, "RedirectStandardInput", "RedirectStandardOutput"); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1973,7 +2025,7 @@ protected override void BeginProcessing() if (_redirectstandardinput.Equals(_redirectstandarderror, StringComparison.OrdinalIgnoreCase)) { message = StringUtil.Format(ProcessResources.DuplicateEntry, "RedirectStandardInput", "RedirectStandardError"); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1987,7 +2039,7 @@ protected override void BeginProcessing() if (_redirectstandardoutput.Equals(_redirectstandarderror, StringComparison.OrdinalIgnoreCase)) { message = StringUtil.Format(ProcessResources.DuplicateEntry, "RedirectStandardOutput", "RedirectStandardError"); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); WriteError(er); return; } @@ -1995,15 +2047,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 = Start(startInfo); + 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(); + } + + 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) { @@ -2014,7 +2140,7 @@ protected override void BeginProcessing() else { message = StringUtil.Format(ProcessResources.CannotStarttheProcess); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } } @@ -2026,23 +2152,20 @@ protected override void BeginProcessing() if (!process.HasExited) { #if UNIX - process.WaitForExit(); + process.WaitForExitAsync(_cancellationTokenSource.Token).GetAwaiter().GetResult(); #else - _waithandle = new ManualResetEvent(false); - - // Create and start the job object - ProcessCollection jobObject = new ProcessCollection(); - 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 } @@ -2050,7 +2173,7 @@ protected override void BeginProcessing() else { message = StringUtil.Format(ProcessResources.CannotStarttheProcess); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } } @@ -2058,58 +2181,34 @@ 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 #region IDisposable Overrides /// - /// Dispose WaitHandle used to honor -Wait parameter. + /// Release all resources. /// + /// + /// Dispose WaitHandle used to honor -Wait parameter. + /// public void Dispose() { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - 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); return filepath; } - private void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary EnvironmentVariables) + private static void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary EnvironmentVariables) { var processEnvironment = startinfo.EnvironmentVariables; foreach (DictionaryEntry entry in EnvironmentVariables) @@ -2119,50 +2218,22 @@ private void LoadEnvironmentVariable(ProcessStartInfo startinfo, IDictionary Env 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 @@ -2200,15 +2271,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) @@ -2269,34 +2333,28 @@ 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 ProcessNativeMethods.SECURITY_ATTRIBUTES(); - - 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 Win32Exception(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 ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } - SafeFileHandle sf = new SafeFileHandle(hFileHandle, true); return sf; } private static StringBuilder BuildCommandLine(string executableFileName, string arguments) { - StringBuilder builder = new StringBuilder(); + StringBuilder builder = new(); string str = executableFileName.Trim(); bool flag = str.StartsWith('"') && str.EndsWith('"'); if (!flag) @@ -2327,10 +2385,10 @@ private static byte[] ConvertEnvVarsToByteArray(StringDictionary sd) string[] strArray2 = new string[sd.Count]; sd.Values.CopyTo(strArray2, 0); Array.Sort(array, strArray2, StringComparer.OrdinalIgnoreCase); - StringBuilder builder = new StringBuilder(); + StringBuilder builder = new(); for (int i = 0; i < sd.Count; i++) { - builder.Append(array[i]);// + builder.Append(array[i]); builder.Append('='); builder.Append(strArray2[i]); builder.Append('\0'); @@ -2340,103 +2398,121 @@ private static byte[] ConvertEnvVarsToByteArray(StringDictionary sd) // Use Unicode encoding bytes = Encoding.Unicode.GetBytes(builder.ToString()); - if (bytes.Length > 0xffff) - { - throw new InvalidOperationException("EnvironmentBlockTooLong"); - } return bytes; } private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods.STARTUPINFO lpStartupInfo, ref int creationFlags) { - // RedirectionStandardInput - if (_redirectstandardinput != null) - { - startinfo.RedirectStandardInput = true; - _redirectstandardinput = ResolveFilePath(_redirectstandardinput); - lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, ProcessNativeMethods.OPEN_EXISTING); - } - else - { - lpStartupInfo.hStdInput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-10), false); - } + // If we are starting a process using the current console window, we need to set its standard handles + // explicitly when they are not redirected because otherwise they won't be set and the new process will + // fail with the "invalid handle" error. + // + // However, if we are starting a process with a new console window, we should not explicitly set those + // standard handles when they are not redirected, but instead let Windows figure out the default to use + // when creating the process. Otherwise, the standard input handles of the current window and the new + // window will get weirdly tied together and cause problems. + bool hasRedirection = startinfo.CreateNoWindow + || _redirectstandardinput is not null + || _redirectstandardoutput is not null + || _redirectstandarderror is not null; + + // RedirectionStandardInput + if (_redirectstandardinput != null) + { + startinfo.RedirectStandardInput = true; + _redirectstandardinput = ResolveFilePath(_redirectstandardinput); + lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, FileMode.Open); + } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdInput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-10), + ownsHandle: false); + } - // RedirectionStandardOutput - if (_redirectstandardoutput != null) - { - startinfo.RedirectStandardOutput = true; - _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); - lpStartupInfo.hStdOutput = GetSafeFileHandleForRedirection(_redirectstandardoutput, ProcessNativeMethods.CREATE_ALWAYS); - } - else - { - lpStartupInfo.hStdOutput = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-11), false); - } + // RedirectionStandardOutput + if (_redirectstandardoutput != null) + { + startinfo.RedirectStandardOutput = true; + _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); + lpStartupInfo.hStdOutput = GetSafeFileHandleForRedirection(_redirectstandardoutput, FileMode.Create); + } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdOutput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-11), + ownsHandle: false); + } - // RedirectionStandardError - if (_redirectstandarderror != null) - { - startinfo.RedirectStandardError = true; - _redirectstandarderror = ResolveFilePath(_redirectstandarderror); - lpStartupInfo.hStdError = GetSafeFileHandleForRedirection(_redirectstandarderror, ProcessNativeMethods.CREATE_ALWAYS); - } - else - { - lpStartupInfo.hStdError = new SafeFileHandle(ProcessNativeMethods.GetStdHandle(-12), false); - } + // RedirectionStandardError + if (_redirectstandarderror != null) + { + startinfo.RedirectStandardError = true; + _redirectstandarderror = ResolveFilePath(_redirectstandarderror); + lpStartupInfo.hStdError = GetSafeFileHandleForRedirection(_redirectstandarderror, FileMode.Create); + } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdError = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-12), + ownsHandle: false); + } - // STARTF_USESTDHANDLES + if (hasRedirection) + { + // Set STARTF_USESTDHANDLES only if there is redirection. lpStartupInfo.dwFlags = 0x100; + } - if (startinfo.CreateNoWindow) - { - // No new window: Inherit the parent process's console window - creationFlags = 0x00000000; - } - else - { - // CREATE_NEW_CONSOLE - creationFlags |= 0x00000010; + if (startinfo.CreateNoWindow) + { + // No new window: Inherit the parent process's console window + creationFlags = 0x00000000; + } + else + { + // CREATE_NEW_CONSOLE + creationFlags |= 0x00000010; - // STARTF_USESHOWWINDOW - lpStartupInfo.dwFlags |= 0x00000001; + // STARTF_USESHOWWINDOW + lpStartupInfo.dwFlags |= 0x00000001; - // On headless SKUs like NanoServer and IoT, window style can only be the default value 'Normal'. - switch (startinfo.WindowStyle) - { - case ProcessWindowStyle.Normal: - // SW_SHOWNORMAL - lpStartupInfo.wShowWindow = 1; - break; - case ProcessWindowStyle.Minimized: - // SW_SHOWMINIMIZED - lpStartupInfo.wShowWindow = 2; - break; - case ProcessWindowStyle.Maximized: - // SW_SHOWMAXIMIZED - lpStartupInfo.wShowWindow = 3; - break; - case ProcessWindowStyle.Hidden: - // SW_HIDE - lpStartupInfo.wShowWindow = 0; - break; - } + // On headless SKUs like NanoServer and IoT, window style can only be the default value 'Normal'. + switch (startinfo.WindowStyle) + { + case ProcessWindowStyle.Normal: + // SW_SHOWNORMAL + lpStartupInfo.wShowWindow = 1; + break; + case ProcessWindowStyle.Minimized: + // SW_SHOWMINIMIZED + lpStartupInfo.wShowWindow = 2; + break; + case ProcessWindowStyle.Maximized: + // SW_SHOWMAXIMIZED + lpStartupInfo.wShowWindow = 3; + break; + case ProcessWindowStyle.Hidden: + // SW_HIDE + lpStartupInfo.wShowWindow = 0; + break; } + } - // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. - creationFlags |= 0x00000004; + // Create the new process suspended so we have a chance to get a corresponding Process object in case it terminates quickly. + creationFlags |= 0x00000004; } /// /// 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 ProcessNativeMethods.STARTUPINFO(); - SafeNativeMethods.PROCESS_INFORMATION lpProcessInformation = new SafeNativeMethods.PROCESS_INFORMATION(); + ProcessNativeMethods.STARTUPINFO lpStartupInfo = new(); + ProcessNativeMethods.PROCESS_INFORMATION lpProcessInformation = new(); int error = 0; - GCHandle pinnedEnvironmentBlock = new GCHandle(); + GCHandle pinnedEnvironmentBlock = new(); IntPtr AddressOfEnvironmentBlock = IntPtr.Zero; string message = string.Empty; @@ -2481,7 +2557,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(); @@ -2500,11 +2576,11 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) } else { - Win32Exception win32ex = new Win32Exception(error); + Win32Exception win32ex = new(error); message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message); } - er = er ?? new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + er ??= new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } @@ -2528,33 +2604,29 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) IntPtr token = WindowsIdentity.GetCurrent().Token; if (!ProcessNativeMethods.CreateEnvironmentBlock(out AddressOfEnvironmentBlock, token, false)) { - Win32Exception win32ex = new Win32Exception(error); + Win32Exception win32ex = new(error); message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message); var errorRecord = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(errorRecord); } } - ProcessNativeMethods.SECURITY_ATTRIBUTES lpProcessAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES(); - ProcessNativeMethods.SECURITY_ATTRIBUTES lpThreadAttributes = new ProcessNativeMethods.SECURITY_ATTRIBUTES(); - flag = ProcessNativeMethods.CreateProcess(null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, lpProcessInformation); + ProcessNativeMethods.SECURITY_ATTRIBUTES lpProcessAttributes = new(); + ProcessNativeMethods.SECURITY_ATTRIBUTES lpThreadAttributes = new(); + flag = ProcessNativeMethods.CreateProcess(null, cmdLine, lpProcessAttributes, lpThreadAttributes, true, creationFlags, AddressOfEnvironmentBlock, startinfo.WorkingDirectory, lpStartupInfo, ref lpProcessInformation); if (!flag) { error = Marshal.GetLastWin32Error(); - Win32Exception win32ex = new Win32Exception(error); + Win32Exception win32ex = new(error); message = StringUtil.Format(ProcessResources.InvalidStartProcess, win32ex.Message); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } 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 { @@ -2568,7 +2640,6 @@ private Process StartWithCreateProcess(ProcessStartInfo startinfo) } lpStartupInfo.Dispose(); - lpProcessInformation.Dispose(); } } #endif @@ -2586,7 +2657,7 @@ private Process StartWithShellExecute(ProcessStartInfo startInfo) catch (Win32Exception ex) { string message = StringUtil.Format(ProcessResources.InvalidStartProcess, ex.Message); - ErrorRecord er = new ErrorRecord(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); + ErrorRecord er = new(new InvalidOperationException(message), "InvalidOperationException", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(er); } @@ -2595,122 +2666,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. - /// - private Microsoft.PowerShell.Commands.SafeJobHandle _jobObjectHandle; - - /// - /// ProcessCollection constructor. + /// Returns completion results for verb parameter. /// - 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 JOBOBJECT_BASIC_PROCESS_ID_LIST(); + // 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 Timer(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); @@ -2726,7 +2795,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)] @@ -2739,22 +2808,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); @@ -2771,7 +2829,16 @@ internal enum LogonFlags } [StructLayout(LayoutKind.Sequential)] - internal class SECURITY_ATTRIBUTES + internal struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential)] + internal sealed class SECURITY_ATTRIBUTES { public int nLength; public SafeLocalMemHandle lpSecurityDescriptor; @@ -2793,14 +2860,13 @@ internal SafeLocalMemHandle() { } - [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle) : base(ownsHandle) { base.SetHandle(existingHandle); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport(PinvokeDllNames.LocalFreeDllName)] + [DllImport(PinvokeDllNames.LocalFreeDllName)] private static extern IntPtr LocalFree(IntPtr hMem); protected override bool ReleaseHandle() @@ -2810,7 +2876,7 @@ protected override bool ReleaseHandle() } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; @@ -2873,72 +2939,6 @@ public void Dispose() } } } - - internal static class SafeNativeMethods - { - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), 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 @@ -2946,7 +2946,6 @@ protected override bool ReleaseHandle() /// /// Non-terminating errors occurring in the process noun commands. /// - [Serializable] public class ProcessCommandException : SystemException { #region ctors @@ -2986,32 +2985,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. - [SecurityPermissionAttribute( - SecurityAction.Demand, - SerializationFormatter = true)] - 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 @@ -3032,4 +3013,3 @@ public string ProcessName #endregion ProcessCommandException } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs index 5a73d16e40e..edc0bbc3a91 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/PropertyCommandBase.cs @@ -4,8 +4,6 @@ using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// 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/RemovePropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs index 1f02a4fc6d1..cc60d91adbe 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RemovePropertyCommand.cs @@ -4,8 +4,6 @@ using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs index 3bed667fb0f..bdefc6b2c64 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RenamePropertyCommand.cs @@ -3,8 +3,6 @@ using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index 8fa26142c5b..d0f5fecf495 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -5,13 +5,11 @@ using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation; - 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")] @@ -61,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 @@ -77,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 @@ -86,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() @@ -101,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) && @@ -128,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); } } @@ -175,4 +288,3 @@ protected override void ProcessRecord() } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs index 297dd5269a9..3d6fc27cc3c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs @@ -28,4 +28,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 9b5c1450c49..a00f9583d6a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -5,18 +5,17 @@ using System; using System.Collections.Generic; -using System.ServiceProcess; +using System.ComponentModel; // Win32Exception using System.Diagnostics.CodeAnalysis; -using Dbg = System.Management.Automation.Diagnostics; using System.Management.Automation; using System.Management.Automation.Internal; -using System.ComponentModel; // Win32Exception -using System.Runtime.Serialization; using System.Runtime.InteropServices; // Marshal, DllImport -using System.Security.Permissions; +using System.Runtime.Serialization; using System.Security.AccessControl; -using NakedWin32Handle = System.IntPtr; +using System.ServiceProcess; +using Dbg = System.Management.Automation.Diagnostics; using DWORD = System.UInt32; +using NakedWin32Handle = System.IntPtr; namespace Microsoft.PowerShell.Commands { @@ -104,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; @@ -135,7 +134,7 @@ internal void SetServiceSecurityDescriptor( if (!status) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); bool accessDenied = exception.NativeErrorCode == NativeMethods.ERROR_ACCESS_DENIED; WriteNonTerminatingError( service, @@ -180,7 +179,7 @@ internal enum SelectionMode /// Select services by Service name. /// ServiceName = 3 - }; + } /// /// Holds the selection mode setting. /// @@ -291,28 +290,19 @@ public ServiceController[] InputObject #region Internal /// - /// Retrieve the master list of all services. + /// Gets an array of all services. /// - /// + /// + /// An array of components that represents all the service 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 ServiceController[] AllServices - { - get - { - if (_allServices == null) - { - _allServices = ServiceController.GetServices(); - } + internal ServiceController[] AllServices => _allServices ??= ServiceController.GetServices(); - return _allServices; - } - } - - private ServiceController[] _allServices = null; + private ServiceController[] _allServices; internal ServiceController GetOneService(string nameOfService) { @@ -381,7 +371,7 @@ private static int ServiceComparison(ServiceController x, ServiceController y) /// private List MatchingServicesByServiceName() { - List matchingServices = new List(); + List matchingServices = new(); if (serviceNames == null) { @@ -444,7 +434,7 @@ private List MatchingServicesByServiceName() /// private List MatchingServicesByDisplayName() { - List matchingServices = new List(); + List matchingServices = new(); if (DisplayName == null) { Diagnostics.Assert(false, "null DisplayName"); @@ -487,7 +477,7 @@ private List MatchingServicesByDisplayName() /// private List MatchingServicesByInput() { - List matchingServices = new List(); + List matchingServices = new(); if (InputObject == null) { Diagnostics.Assert(false, "null InputObject"); @@ -620,146 +610,197 @@ 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 + + /// + /// Writes a verbose message when a service property query fails. + /// + /// Name of the service. + /// Name of the property that failed to be queried. + private void WriteServicePropertyError(string serviceName, string propertyName) + { + Win32Exception e = new(Marshal.GetLastWin32Error()); + WriteVerbose( + StringUtil.Format( + ServiceResources.CouldNotGetServiceProperty, + serviceName, + propertyName, + e.Message)); + } + /// /// 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 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 (IntPtr.Zero == hScManager) - { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(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 (IntPtr.Zero == hService) + if (hService != nint.Zero) { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - } - - NativeMethods.SERVICE_DESCRIPTIONW description = new NativeMethods.SERVICE_DESCRIPTIONW(); - bool querySuccessful = NativeMethods.QueryServiceConfig2(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out description); - - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); - 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; + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DESCRIPTIONW)); + } - NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = new NativeMethods.QUERY_SERVICE_CONFIG(); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceInfo); + if (NativeMethods.QueryServiceConfig2( + hService, + NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + out NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo)) + { + isDelayedAutoStart = autostartInfo.fDelayedAutostart; + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + } - if (!querySuccessful) + if (NativeMethods.QueryServiceConfig( + hService, + out NativeMethods.QUERY_SERVICE_CONFIG serviceInfo)) + { + binPath = serviceInfo.lpBinaryPathName; + startName = serviceInfo.lpServiceStartName; + if (isDelayedAutoStart.HasValue) + { + startupType = NativeMethods.GetServiceStartupType( + (ServiceStartMode)serviceInfo.dwStartType, + isDelayedAutoStart.Value); + } + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + } + else { - WriteNonTerminatingError( - service: service, - innerException: null, - errorId: "CouldNotGetServiceInfo", - errorMessage: ServiceResources.CouldNotGetServiceInfo, - category: ErrorCategory.PermissionDenied - ); + // handle when OpenServiceW itself fails: + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_QUERY_CONFIG)); } - - PSProperty noteProperty = new PSProperty("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 (IntPtr.Zero != hService) + if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); - if (!succeeded) - { - Diagnostics.Assert(lastError != 0, "ErrorCode not success"); - } - } - - if (IntPtr.Zero != hScManager) - { - 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 @@ -857,7 +898,7 @@ internal bool DoWaitForStatus( string errorId, string errorMessage) { - do + while (true) { try { @@ -891,14 +932,14 @@ internal bool DoWaitForStatus( // will throw PipelineStoppedException if user hit CTRL-C WriteWarning(message); } - } while (true); + } } /// /// 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; @@ -908,14 +949,13 @@ internal bool DoStartService(ServiceController serviceController) } catch (Win32Exception e) { - if (NativeMethods.ERROR_SERVICE_ALREADY_RUNNING != e.NativeErrorCode) + if (e.NativeErrorCode != NativeMethods.ERROR_SERVICE_ALREADY_RUNNING) exception = e; } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner == null - || NativeMethods.ERROR_SERVICE_ALREADY_RUNNING != eInner.NativeErrorCode) + if (e.InnerException is not Win32Exception eInner + || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_ALREADY_RUNNING) { exception = e; } @@ -955,13 +995,13 @@ 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 // if the service is not running, but this is not an error. - List stoppedServices = new List(); + List stoppedServices = new(); ServiceController[] dependentServices = null; try @@ -1025,15 +1065,13 @@ internal List DoStopService(ServiceController serviceControll } catch (Win32Exception e) { - if (NativeMethods.ERROR_SERVICE_NOT_ACTIVE != e.NativeErrorCode) + if (e.NativeErrorCode != NativeMethods.ERROR_SERVICE_NOT_ACTIVE) exception = e; } catch (InvalidOperationException e) { - Win32Exception eInner = - e.InnerException as Win32Exception; - if (eInner == null - || NativeMethods.ERROR_SERVICE_NOT_ACTIVE != eInner.NativeErrorCode) + if (e.InnerException is not Win32Exception eInner + || eInner.NativeErrorCode != NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { exception = e; } @@ -1086,9 +1124,9 @@ internal List DoStopService(ServiceController serviceControll /// True if all dependent services are stopped /// False if not all dependent services are stopped /// - private bool HaveAllDependentServicesStopped(ServiceController[] dependentServices) + 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); } /// @@ -1107,7 +1145,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; @@ -1118,7 +1156,7 @@ internal bool DoPauseService(ServiceController serviceController) } catch (Win32Exception e) { - if (NativeMethods.ERROR_SERVICE_NOT_ACTIVE == e.NativeErrorCode) + if (e.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } @@ -1127,9 +1165,8 @@ internal bool DoPauseService(ServiceController serviceController) } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner != null - && NativeMethods.ERROR_SERVICE_NOT_ACTIVE == eInner.NativeErrorCode) + if (e.InnerException is Win32Exception eInner + && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } @@ -1188,7 +1225,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; @@ -1199,7 +1236,7 @@ internal bool DoResumeService(ServiceController serviceController) } catch (Win32Exception e) { - if (NativeMethods.ERROR_SERVICE_NOT_ACTIVE == e.NativeErrorCode) + if (e.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } @@ -1208,9 +1245,8 @@ internal bool DoResumeService(ServiceController serviceController) } catch (InvalidOperationException e) { - Win32Exception eInner = e.InnerException as Win32Exception; - if (eInner != null - && NativeMethods.ERROR_SERVICE_NOT_ACTIVE == eInner.NativeErrorCode) + if (e.InnerException is Win32Exception eInner + && eInner.NativeErrorCode == NativeMethods.ERROR_SERVICE_NOT_ACTIVE) { serviceNotRunning = true; } @@ -1496,7 +1532,10 @@ public class SetServiceCommand : ServiceOperationBaseCommand [Alias("ServiceName", "SN")] public new string Name { - get { return serviceName; } + get + { + return serviceName; + } set { @@ -1524,7 +1563,10 @@ public class SetServiceCommand : ServiceOperationBaseCommand [Alias("DN")] public new string DisplayName { - get { return displayName; } + get + { + return displayName; + } set { @@ -1553,7 +1595,10 @@ public class SetServiceCommand : ServiceOperationBaseCommand [ValidateNotNullOrEmpty] public string Description { - get { return description; } + get + { + return description; + } set { @@ -1576,7 +1621,10 @@ public string Description [ValidateNotNullOrEmpty] public ServiceStartupType StartupType { - get { return startupType; } + get + { + return startupType; + } set { @@ -1612,7 +1660,10 @@ public string SecurityDescriptorSddl [ValidateSetAttribute(new string[] { "Running", "Stopped", "Paused" })] public string Status { - get { return serviceStatus; } + get + { + return serviceStatus; + } set { @@ -1675,7 +1726,6 @@ public string Status #region Overrides /// /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -1706,14 +1756,14 @@ protected override void ProcessRecord() catch (ArgumentException ex) { // cannot use WriteNonterminatingError as service is null - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.ObjectNotFound, Name); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.ObjectNotFound, Name); WriteError(er); return; } catch (InvalidOperationException ex) { // cannot use WriteNonterminatingError as service is null - ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, Name); + ErrorRecord er = new(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, Name); WriteError(er); return; } @@ -1738,10 +1788,10 @@ protected override void ProcessRecord() NativeMethods.SC_MANAGER_CONNECT ); - if (IntPtr.Zero == hScManager) + if (hScManager == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1751,16 +1801,20 @@ 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 (IntPtr.Zero == hService) + if (hService == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1771,7 +1825,7 @@ protected override void ProcessRecord() } // Modify startup type or display name or credential if (!string.IsNullOrEmpty(DisplayName) - || ServiceStartupType.InvalidValue != StartupType || Credential != null) + || StartupType != ServiceStartupType.InvalidValue || Credential != null) { DWORD dwStartType = NativeMethods.SERVICE_NO_CHANGE; if (!NativeMethods.TryGetNativeStartupType(StartupType, out dwStartType)) @@ -1806,7 +1860,7 @@ protected override void ProcessRecord() if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1817,7 +1871,7 @@ protected override void ProcessRecord() } } - NativeMethods.SERVICE_DESCRIPTIONW sd = new NativeMethods.SERVICE_DESCRIPTIONW(); + NativeMethods.SERVICE_DESCRIPTIONW sd = new(); sd.lpDescription = Description; int size = Marshal.SizeOf(sd); IntPtr buffer = Marshal.AllocCoTaskMem(size); @@ -1831,7 +1885,7 @@ protected override void ProcessRecord() if (!status) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1841,7 +1895,7 @@ protected override void ProcessRecord() } // Set the delayed auto start - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new(); ds.fDelayedAutostart = StartupType == ServiceStartupType.AutomaticDelayedStart; size = Marshal.SizeOf(ds); delayedAutoStartInfoBuffer = Marshal.AllocCoTaskMem(size); @@ -1855,7 +1909,7 @@ protected override void ProcessRecord() if (!status) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -1915,24 +1969,24 @@ protected override void ProcessRecord() if (PassThru.IsPresent) { // To display the service, refreshing the service would not show the display name after updating - ServiceController displayservice = new ServiceController(Name); + ServiceController displayservice = new(Name); WriteObject(displayservice); } } finally { - if (IntPtr.Zero != delayedAutoStartInfoBuffer) + if (delayedAutoStartInfoBuffer != IntPtr.Zero) { Marshal.FreeCoTaskMem(delayedAutoStartInfoBuffer); } - if (IntPtr.Zero != hService) + if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1942,13 +1996,13 @@ protected override void ProcessRecord() } } - if (IntPtr.Zero != hScManager) + if (hScManager != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hScManager); if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -1961,7 +2015,7 @@ protected override void ProcessRecord() } finally { - if (IntPtr.Zero != password) + if (password != IntPtr.Zero) { Marshal.ZeroFreeCoTaskMemUnicode(password); } @@ -2106,7 +2160,6 @@ public string[] DependsOn /// /// Create the service. /// - [ArchitectureSensitive] protected override void BeginProcessing() { ServiceController service = null; @@ -2134,10 +2187,10 @@ protected override void BeginProcessing() null, NativeMethods.SC_MANAGER_CONNECT | NativeMethods.SC_MANAGER_CREATE_SERVICE ); - if (IntPtr.Zero == hScManager) + if (hScManager == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2211,10 +2264,10 @@ protected override void BeginProcessing() username, password ); - if (IntPtr.Zero == hService) + if (hService == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2227,7 +2280,7 @@ protected override void BeginProcessing() } // Set the service description - NativeMethods.SERVICE_DESCRIPTIONW sd = new NativeMethods.SERVICE_DESCRIPTIONW(); + NativeMethods.SERVICE_DESCRIPTIONW sd = new(); sd.lpDescription = Description; int size = Marshal.SizeOf(sd); IntPtr buffer = Marshal.AllocCoTaskMem(size); @@ -2241,7 +2294,7 @@ protected override void BeginProcessing() if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2255,7 +2308,7 @@ protected override void BeginProcessing() // Set the delayed auto start if (StartupType == ServiceStartupType.AutomaticDelayedStart) { - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new NativeMethods.SERVICE_DELAYED_AUTO_START_INFO(); + NativeMethods.SERVICE_DELAYED_AUTO_START_INFO ds = new(); ds.fDelayedAutostart = true; size = Marshal.SizeOf(ds); delayedAutoStartInfoBuffer = Marshal.AllocCoTaskMem(size); @@ -2269,7 +2322,7 @@ protected override void BeginProcessing() if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2293,23 +2346,23 @@ protected override void BeginProcessing() } finally { - if (IntPtr.Zero != delayedAutoStartInfoBuffer) + if (delayedAutoStartInfoBuffer != IntPtr.Zero) { Marshal.FreeCoTaskMem(delayedAutoStartInfoBuffer); } - if (IntPtr.Zero != password) + if (password != IntPtr.Zero) { Marshal.ZeroFreeCoTaskMemUnicode(password); } - if (IntPtr.Zero != hService) + if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2321,13 +2374,13 @@ protected override void BeginProcessing() } } - if (IntPtr.Zero != hScManager) + if (hScManager != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hScManager); if (!succeeded) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( Name, DisplayName, @@ -2348,7 +2401,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 @@ -2375,7 +2428,6 @@ public class RemoveServiceCommand : ServiceBaseCommand /// /// Remove the service. /// - [ArchitectureSensitive] protected override void ProcessRecord() { ServiceController service = null; @@ -2403,14 +2455,14 @@ protected override void ProcessRecord() catch (ArgumentException ex) { // Cannot use WriteNonterminatingError as service is null - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.ObjectNotFound, Name); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.ObjectNotFound, Name); WriteError(er); return; } catch (InvalidOperationException ex) { // Cannot use WriteNonterminatingError as service is null - ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, Name); + ErrorRecord er = new(ex, "InvalidOperationException", ErrorCategory.ObjectNotFound, Name); WriteError(er); return; } @@ -2433,10 +2485,10 @@ protected override void ProcessRecord() lpDatabaseName: null, dwDesiredAccess: NativeMethods.SC_MANAGER_ALL_ACCESS ); - if (IntPtr.Zero == hScManager) + if (hScManager == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteObject(exception); WriteNonTerminatingError( service, @@ -2452,10 +2504,10 @@ protected override void ProcessRecord() Name, NativeMethods.SERVICE_DELETE ); - if (IntPtr.Zero == hService) + if (hService == IntPtr.Zero) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -2470,7 +2522,7 @@ protected override void ProcessRecord() if (!status) { int lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new Win32Exception(lastError); + Win32Exception exception = new(lastError); WriteNonTerminatingError( service, exception, @@ -2481,7 +2533,7 @@ protected override void ProcessRecord() } finally { - if (IntPtr.Zero != hService) + if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); if (!succeeded) @@ -2491,7 +2543,7 @@ protected override void ProcessRecord() } } - if (IntPtr.Zero != hScManager) + if (hScManager != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hScManager); if (!succeeded) @@ -2518,7 +2570,6 @@ protected override void ProcessRecord() /// /// Non-terminating errors occurring in the service noun commands. /// - [Serializable] public class ServiceCommandException : SystemException { #region ctors @@ -2560,32 +2611,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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -2708,7 +2739,7 @@ internal struct SERVICE_DESCRIPTIONW { [MarshalAs(UnmanagedType.LPWStr)] internal string lpDescription; - }; + } [StructLayout(LayoutKind.Sequential)] internal struct QUERY_SERVICE_CONFIG @@ -2722,13 +2753,13 @@ internal struct QUERY_SERVICE_CONFIG [MarshalAs(UnmanagedType.LPWStr)] internal string lpDependencies; [MarshalAs(UnmanagedType.LPWStr)] internal string lpServiceStartName; [MarshalAs(UnmanagedType.LPWStr)] internal string lpDisplayName; - }; + } [StructLayout(LayoutKind.Sequential)] internal struct SERVICE_DELAYED_AUTO_START_INFO { internal bool fDelayedAutostart; - }; + } [DllImport(PinvokeDllNames.CreateServiceWDllName, CharSet = CharSet.Unicode, SetLastError = true)] internal static extern @@ -2757,69 +2788,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; @@ -2831,7 +2799,7 @@ internal static bool QueryServiceConfig(NakedWin32Handle hService, out NativeMet cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); - if (status != true && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) + if (!status && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) { return status; } @@ -2869,7 +2837,7 @@ internal static bool QueryServiceConfig2(NakedWin32Handle hService, DWORD inf cbBufSize: 0, pcbBytesNeeded: out bufferSizeNeeded); - if (status != true && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) + if (!status && Marshal.GetLastWin32Error() != ERROR_INSUFFICIENT_BUFFER) { return status; } @@ -2956,20 +2924,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 bcb7330c674..49ab5d768f6 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetClipboardCommand.cs @@ -17,9 +17,10 @@ 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 List _contentList = new List(); + private readonly List _contentList = new(); /// /// Property that sets clipboard content. @@ -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); + } } } @@ -84,7 +103,7 @@ private void SetClipboardContent(List contentList, bool append) return; } - StringBuilder content = new StringBuilder(); + StringBuilder content = new(); if (append) { content.AppendLine(Clipboard.GetText()); @@ -102,7 +121,7 @@ private void SetClipboardContent(List contentList, bool append) if (verboseString.Length >= 20) { verboseString = verboseString.Substring(0, 20); - verboseString = verboseString + " ..."; + verboseString += " ..."; } } @@ -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/SetContentCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetContentCommand.cs index 4e040517b02..44f4d904d33 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetContentCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetContentCommand.cs @@ -4,8 +4,6 @@ using System.Management.Automation; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -31,7 +29,7 @@ internal override void BeforeOpenStreams(string[] paths) throw PSTraceSource.NewArgumentNullException(nameof(paths)); } - CmdletProviderContext context = new CmdletProviderContext(GetCurrentContext()); + CmdletProviderContext context = new(GetCurrentContext()); foreach (string path in paths) { @@ -90,4 +88,3 @@ internal override bool CallShouldProcess(string path) #endregion protected members } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs index 9e2018342ba..7c5f43aad2b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/SetPropertyCommand.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -46,7 +43,10 @@ public string[] Path [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return paths; } + get + { + return paths; + } set { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs index 4f5c239cc64..58dcfbd1daa 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; } @@ -107,4 +107,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index c5d4f0395fa..2a4a453f8f7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -4,7 +4,6 @@ #nullable enable using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; @@ -28,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 @@ -56,9 +56,9 @@ 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 CancellationTokenSource(); + private readonly CancellationTokenSource _dnsLookupCancel = new(); private bool _disposed; @@ -135,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; @@ -144,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; @@ -170,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; } @@ -179,7 +182,14 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// With this switch, standard ping and -Traceroute returns only true / false, and -MtuSize returns an integer. /// [Parameter] - public SwitchParameter Quiet; + 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. @@ -228,14 +238,17 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// /// BeginProcessing implementation for TestConnectionCommand. + /// Sets Count for different types of tests unless specified explicitly. /// protected override void BeginProcessing() { - switch (ParameterSetName) + if (Repeat) { - case RepeatPingParameterSet: - Count = int.MaxValue; - break; + Count = int.MaxValue; + } + else if (ParameterSetName == TcpPortParameterSet) + { + SetCountForTcpTest(); } } @@ -251,21 +264,22 @@ protected override void ProcessRecord() foreach (var targetName in TargetName) { - switch (ParameterSetName) + if (MtuSize) + { + ProcessMTUSize(targetName); + } + else if (Traceroute) { - case DefaultPingParameterSet: - case RepeatPingParameterSet: - ProcessPing(targetName); - break; - case MtuSizeDetectParameterSet: - ProcessMTUSize(targetName); - break; - case TraceRouteParameterSet: - ProcessTraceroute(targetName); - break; - case TcpPortParameterSet: - ProcessConnectionByTCPPort(targetName); - break; + ProcessTraceroute(targetName); + } + else if (ParameterSetName == TcpPortParameterSet) + { + ProcessConnectionByTCPPort(targetName); + } + else + { + // None of the switch parameters are true: handle default ping or -Repeat + ProcessPing(targetName); } } } @@ -282,6 +296,18 @@ protected override void StopProcessing() #region ConnectionTest + private void SetCountForTcpTest() + { + if (Repeat) + { + Count = int.MaxValue; + } + else if (!MyInvocation.BoundParameters.ContainsKey(nameof(Count))) + { + Count = 1; + } + } + private void ProcessConnectionByTCPPort(string targetNameOrAddress) { if (!TryResolveNameOrAddress(targetNameOrAddress, out _, out IPAddress? targetAddress)) @@ -294,42 +320,80 @@ private void ProcessConnectionByTCPPort(string targetNameOrAddress) return; } - TcpClient client = new TcpClient(); + 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(); + } + + if (!Detailed.IsPresent) + { + WriteObject(status == SocketError.Success); + return; + } + else + { + WriteObject(new TcpPortStatus( + i, + Source, + targetNameOrAddress, + targetAddress, + TcpPort, + latency, + status == SocketError.Success, + status + )); + } - WriteObject(false); + if (i < Count) + { + Task.Delay(delayMilliseconds).Wait(_dnsLookupCancel.Token); + } + } } #endregion ConnectionTest @@ -351,11 +415,11 @@ private void ProcessTraceroute(string targetNameOrAddress) } int currentHop = 1; - PingOptions pingOptions = new PingOptions(currentHop, DontFragment.IsPresent); + PingOptions pingOptions = new(currentHop, DontFragment.IsPresent); PingReply reply; PingReply discoveryReply; int timeout = TimeoutSeconds * 1000; - Stopwatch timer = new Stopwatch(); + Stopwatch timer = new(); IPAddress hopAddress; do @@ -420,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, @@ -437,7 +504,7 @@ private void ProcessTraceroute(string targetNameOrAddress) resolvedTargetName, ex.Message); Exception pingException = new PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -498,9 +565,11 @@ private void ProcessMTUSize(string targetNameOrAddress) int LowMTUSize = targetAddress.AddressFamily == AddressFamily.InterNetworkV6 ? 1280 : 68; int timeout = TimeoutSeconds * 1000; + PingReply? timeoutReply = null; + try { - PingOptions pingOptions = new PingOptions(MaxHops, true); + PingOptions pingOptions = new(MaxHops, true); int retry = 1; while (LowMTUSize < (HighMTUSize - 1)) @@ -518,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) @@ -536,7 +606,7 @@ private void ProcessMTUSize(string targetNameOrAddress) targetAddress, reply.Status.ToString()); Exception pingException = new PingException(message); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -561,7 +631,7 @@ private void ProcessMTUSize(string targetNameOrAddress) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, targetAddress, ex.Message); Exception pingException = new PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -576,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)); + } + } } @@ -604,7 +695,7 @@ private void ProcessPing(string targetNameOrAddress) byte[] buffer = GetSendBuffer(BufferSize); PingReply reply; - PingOptions pingOptions = new PingOptions(MaxHops, DontFragment.IsPresent); + PingOptions pingOptions = new(MaxHops, DontFragment.IsPresent); int timeout = TimeoutSeconds * 1000; int delay = Delay * 1000; @@ -618,7 +709,7 @@ private void ProcessPing(string targetNameOrAddress) { string message = StringUtil.Format(TestConnectionResources.NoPingResult, resolvedTargetName, ex.Message); Exception pingException = new PingException(message, ex.InnerException); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -641,7 +732,7 @@ private void ProcessPing(string targetNameOrAddress) resolvedTargetName, reply, reply.RoundtripTime, - buffer.Length, + buffer.Length == 0 ? DefaultSendBufferSize : buffer.Length, pingNum: (uint)i)); } @@ -679,7 +770,7 @@ private bool TryResolveNameOrAddress( resolvedTargetName, TestConnectionResources.TargetAddressAbsent); Exception pingException = new PingException(message, null); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -723,7 +814,7 @@ private bool TryResolveNameOrAddress( resolvedTargetName, TestConnectionResources.CannotResolveTargetName); Exception pingException = new PingException(message, ex); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -745,7 +836,7 @@ private bool TryResolveNameOrAddress( resolvedTargetName, TestConnectionResources.TargetAddressAbsent); Exception pingException = new PingException(message, null); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( pingException, TestConnectionExceptionId, ErrorCategory.ResourceUnavailable, @@ -794,9 +885,9 @@ private IPHostEntry GetCancellableHostEntry(string targetNameOrAddress) // Users most often use the default buffer size so we cache the buffer. // Creates and fills a send buffer. This follows the ping.exe and CoreFX model. - private byte[] GetSendBuffer(int bufferSize) + private static byte[] GetSendBuffer(int bufferSize) { - if (bufferSize == DefaultSendBufferSize && s_DefaultSendBuffer != null) + if (bufferSize == DefaultSendBufferSize) { return s_DefaultSendBuffer; } @@ -808,11 +899,6 @@ private byte[] GetSendBuffer(int bufferSize) sendBuffer[i] = (byte)((int)'a' + i % 23); } - if (bufferSize == DefaultSendBufferSize && s_DefaultSendBuffer == null) - { - s_DefaultSendBuffer = sendBuffer; - } - return sendBuffer; } @@ -838,6 +924,7 @@ protected virtual void Dispose(bool disposing) if (disposing) { _sender?.Dispose(); + _dnsLookupCancel.Dispose(); } _disposed = true; @@ -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 86% rename from src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs rename to src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs index 29b905cb0ff..50765c0e0ae 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/PingPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs @@ -4,8 +4,6 @@ using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -64,7 +62,10 @@ public string[] Path [AllowEmptyString] public string[] LiteralPath { - get { return _paths; } + get + { + return _paths; + } set { @@ -135,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) { @@ -185,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; } @@ -209,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); } } } @@ -244,7 +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 106c9038981..22a50e41176 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 { @@ -53,7 +54,7 @@ protected override void ProcessRecord() // make sure we've got the latest time zone settings TimeZoneInfo.ClearCachedData(); - if (this.ParameterSetName.Equals("ListAvailable", StringComparison.OrdinalIgnoreCase)) + if (ListAvailable) { // output the list of all available time zones WriteObject(TimeZoneInfo.GetSystemTimeZones(), true); @@ -82,7 +83,7 @@ protected override void ProcessRecord() foreach (string tzname in Name) { TimeZoneInfo[] timeZones = TimeZoneHelper.LookupSystemTimeZoneInfoByName(tzname); - if (0 < timeZones.Length) + if (timeZones.Length > 0) { // manually process each object in the array, so if there is only a single // entry then the returned type is TimeZoneInfo and not TimeZoneInfo[], and @@ -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() @@ -188,7 +190,7 @@ protected override void ProcessRecord() { // lookup the time zone name and make sure we have one (and only one) match TimeZoneInfo[] timeZones = TimeZoneHelper.LookupSystemTimeZoneInfoByName(Name); - if (0 == timeZones.Length) + if (timeZones.Length == 0) { string message = string.Format(CultureInfo.InvariantCulture, TimeZoneResources.TimeZoneNameNotFound, Name); @@ -198,7 +200,7 @@ protected override void ProcessRecord() ErrorCategory.InvalidArgument, "Name")); } - else if (1 < timeZones.Length) + else if (timeZones.Length > 1) { string message = string.Format(CultureInfo.InvariantCulture, TimeZoneResources.MultipleMatchingTimeZones, Name); @@ -254,14 +256,14 @@ protected override void ProcessRecord() try { // construct and populate a new DYNAMIC_TIME_ZONE_INFORMATION structure - NativeMethods.DYNAMIC_TIME_ZONE_INFORMATION dtzi = new NativeMethods.DYNAMIC_TIME_ZONE_INFORMATION(); + NativeMethods.DYNAMIC_TIME_ZONE_INFORMATION dtzi = new(); dtzi.Bias -= (int)InputObject.BaseUtcOffset.TotalMinutes; dtzi.StandardName = InputObject.StandardName; dtzi.DaylightName = InputObject.DaylightName; dtzi.TimeZoneKeyName = InputObject.Id; // Request time zone transition information for the current year - NativeMethods.TIME_ZONE_INFORMATION tzi = new NativeMethods.TIME_ZONE_INFORMATION(); + NativeMethods.TIME_ZONE_INFORMATION tzi = new(); if (!NativeMethods.GetTimeZoneInformationForYear((ushort)DateTime.Now.Year, ref dtzi, ref tzi)) { ThrowWin32Error(); @@ -343,7 +345,7 @@ protected bool HasAccess try { // setup the privileges being checked - NativeMethods.PRIVILEGE_SET ps = new NativeMethods.PRIVILEGE_SET() + NativeMethods.PRIVILEGE_SET ps = new() { PrivilegeCount = 1, Control = 1, @@ -390,7 +392,7 @@ protected void SetAccessToken(bool enable) try { // setup the privileges being requested - NativeMethods.TOKEN_PRIVILEGES tp = new NativeMethods.TOKEN_PRIVILEGES() + NativeMethods.TOKEN_PRIVILEGES tp = new() { PrivilegeCount = 1, Luid = 0, @@ -428,15 +430,8 @@ protected void ThrowWin32Error() #region Win32 interop helper - internal class NativeMethods + internal static class NativeMethods { - /// - /// Private constructor to prevent instantiation. - /// - private NativeMethods() - { - } - #region Native DLL locations private const string SetDynamicTimeZoneApiDllName = "api-ms-win-core-timezone-l1-1-0.dll"; @@ -779,8 +774,8 @@ internal static class TimeZoneHelper /// A TimeZoneInfo object array containing information about the specified system time zones. internal static TimeZoneInfo[] LookupSystemTimeZoneInfoByName(string name) { - WildcardPattern namePattern = new WildcardPattern(name, WildcardOptions.IgnoreCase); - List tzi = new List(); + WildcardPattern namePattern = new(name, WildcardOptions.IgnoreCase); + List tzi = new(); // get the available system time zones ReadOnlyCollection zones = TimeZoneInfo.GetSystemTimeZones(); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs index 056cf60265b..67d3acee44a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs @@ -91,4 +91,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/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..3f91d597e57 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs @@ -475,7 +475,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 7ccf63cdf8e..a2a6387e9da 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WriteContentCommandBase.cs @@ -8,8 +8,6 @@ using System.Management.Automation; using System.Management.Automation.Provider; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -97,10 +95,7 @@ protected override void ProcessRecord() // Initialize the content - if (_content == null) - { - _content = Array.Empty(); - } + _content ??= Array.Empty(); if (_pipingPaths) { @@ -145,7 +140,7 @@ protected override void ProcessRecord() catch (Exception e) // Catch-all OK. 3rd party callout { ProviderInvocationException providerException = - new ProviderInvocationException( + new( "ProviderContentWriteError", SessionStateStrings.ProviderContentWriteError, holder.PathInfo.Provider, @@ -260,7 +255,7 @@ internal List GetContentWriters( // Create the results array - List results = new List(); + List results = new(); foreach (PathInfo pathInfo in pathInfos) { @@ -313,7 +308,7 @@ internal List GetContentWriters( if (writers.Count == 1 && writers[0] != null) { ContentHolder holder = - new ContentHolder(pathInfo, null, writers[0]); + new(pathInfo, null, writers[0]); results.Add(holder); } 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..c19f753529f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -1,17 +1,17 @@ - @@ -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,9 +208,12 @@ 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}. + + Could not retrieve property '{1}' for service '{0}': {2} + 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 806dafd2f68..cc98893c5b6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -2,13 +2,14 @@ PowerShell's Microsoft.PowerShell.Commands.Utility project - $(NoWarn);CS1570 + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.Commands.Utility - + + @@ -31,10 +32,9 @@ - - - - + + + diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs index 2ea6d6e20ae..4c7601e883f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs @@ -20,7 +20,7 @@ namespace Microsoft.PowerShell.Commands HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097109", RemotingCapability = RemotingCapability.None)] public class AddMemberCommand : PSCmdlet { - private static readonly object s_notSpecified = new object(); + private static readonly object s_notSpecified = new(); private static bool HasBeenSpecified(object obj) { @@ -37,9 +37,9 @@ private static bool HasBeenSpecified(object obj) [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = NotePropertyMultiMemberSet)] public PSObject InputObject { - set { _inputObject = value; } - get { return _inputObject; } + + set { _inputObject = value; } } private PSMemberTypes _memberType; @@ -50,9 +50,9 @@ public PSObject InputObject [Alias("Type")] public PSMemberTypes MemberType { - set { _memberType = value; } - get { return _memberType; } + + set { _memberType = value; } } private string _memberName; @@ -62,9 +62,9 @@ public PSMemberTypes MemberType [Parameter(Mandatory = true, Position = 1, ParameterSetName = "MemberSet")] public string Name { - set { _memberName = value; } - get { return _memberName; } + + set { _memberName = value; } } private object _value1 = s_notSpecified; @@ -74,9 +74,9 @@ public string Name [Parameter(Position = 2, ParameterSetName = "MemberSet")] public object Value { - set { _value1 = value; } - get { return _value1; } + + set { _value1 = value; } } private object _value2 = s_notSpecified; @@ -86,9 +86,9 @@ public object Value [Parameter(Position = 3, ParameterSetName = "MemberSet")] public object SecondValue { - set { _value2 = value; } - get { return _value2; } + + set { _value2 = value; } } private string _typeName; @@ -102,9 +102,9 @@ public object SecondValue [ValidateNotNullOrEmpty] public string TypeName { - set { _typeName = value; } - get { return _typeName; } + + set { _typeName = value; } } private bool _force; @@ -116,9 +116,9 @@ public string TypeName [Parameter(ParameterSetName = NotePropertyMultiMemberSet)] public SwitchParameter Force { - set { _force = value; } - get { return _force; } + + set { _force = value; } } private bool _passThru /* = false */; @@ -132,9 +132,9 @@ public SwitchParameter Force [Parameter(ParameterSetName = NotePropertyMultiMemberSet)] public SwitchParameter PassThru { - set { _passThru = value; } - get { return _passThru; } + + set { _passThru = value; } } #region Simplifying NoteProperty Declaration @@ -147,14 +147,14 @@ 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 { - set { _notePropertyName = value; } - get { return _notePropertyName; } + + set { _notePropertyName = value; } } private object _notePropertyValue; @@ -165,9 +165,9 @@ public string NotePropertyName [AllowNull] public object NotePropertyValue { - set { _notePropertyValue = value; } - get { return _notePropertyValue; } + + set { _notePropertyValue = value; } } // Use IDictionary to support both Hashtable and OrderedHashtable @@ -221,7 +221,7 @@ private void EnsureValue1HasBeenSpecified() { if (!HasBeenSpecified(_value1)) { - Collection fdc = new Collection(); + Collection fdc = new(); fdc.Add(new FieldDescription("Value")); string prompt = StringUtil.Format(AddMember.Value1Prompt, _memberType); Dictionary result = this.Host.UI.Prompt(prompt, null, fdc); @@ -388,7 +388,7 @@ protected override void ProcessRecord() memberCount++; } - memberCountHelper = memberCountHelper >> 1; + memberCountHelper >>= 1; } if (memberCount != 1) @@ -524,16 +524,19 @@ 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); } private ErrorRecord NewError(string errorId, string resourceId, object targetObject, params object[] args) { - ErrorDetails details = new ErrorDetails(this.GetType().GetTypeInfo().Assembly, + ErrorDetails details = new(this.GetType().GetTypeInfo().Assembly, "Microsoft.PowerShell.Commands.Utility.resources.AddMember", resourceId, args); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidOperation, @@ -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 4f7185a7e85..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(); @@ -153,7 +155,7 @@ public string[] Path string[] pathValue = value; - List resolvedPaths = new List(); + List resolvedPaths = new(); // Verify that the paths are resolved and valid foreach (string path in pathValue) @@ -198,7 +200,7 @@ public string[] LiteralPath return; } - List resolvedPaths = new List(); + List resolvedPaths = new(); foreach (string path in value) { string literalPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(path); @@ -230,7 +232,7 @@ private void ProcessPaths(List resolvedPaths) // Throw an error if it is an unrecognized extension default: - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception( StringUtil.Format(AddTypeStrings.FileExtensionNotSupported, currentExtension)), "EXTENSION_NOT_SUPPORTED", @@ -248,7 +250,7 @@ private void ProcessPaths(List resolvedPaths) else if (!string.Equals(activeExtension, currentExtension, StringComparison.OrdinalIgnoreCase)) { // All files must have the same extension otherwise throw. - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception( StringUtil.Format(AddTypeStrings.MultipleExtensionsNotSupported)), "MULTIPLE_EXTENSION_NOT_SUPPORTED", @@ -292,11 +294,17 @@ private void ProcessPaths(List resolvedPaths) [Alias("RA")] public string[] ReferencedAssemblies { - get { return _referencedAssemblies; } + get + { + return _referencedAssemblies; + } set { - if (value != null) { _referencedAssemblies = value; } + if (value != null) + { + _referencedAssemblies = value; + } } } @@ -327,7 +335,7 @@ public string OutputAssembly // Try to resolve the path ProviderInfo provider = null; - Collection newPaths = new Collection(); + Collection newPaths = new(); try { @@ -336,7 +344,7 @@ public string OutputAssembly // Ignore the ItemNotFound -- we handle it. catch (ItemNotFoundException) { } - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception( StringUtil.Format(AddTypeStrings.OutputAssemblyDidNotResolve, _outputAssembly)), "INVALID_OUTPUT_ASSEMBLY", @@ -397,7 +405,7 @@ public string OutputAssembly /// /// Flag to pass the resulting types along. /// - [Parameter()] + [Parameter] public SwitchParameter PassThru { get; set; } /// @@ -470,7 +478,7 @@ private string GenerateTypeSource(string typeNamespace, string typeName, string } // Get the -FromMember template for a given language - private string GetMethodTemplate(Language language) + private static string GetMethodTemplate(Language language) { switch (language) { @@ -486,7 +494,7 @@ private string GetMethodTemplate(Language language) } // Get the -FromMember namespace template for a given language - private string GetNamespaceTemplate(Language language) + private static string GetNamespaceTemplate(Language language) { switch (language) { @@ -502,7 +510,7 @@ private string GetNamespaceTemplate(Language language) } // Get the -FromMember namespace template for a given language - private string GetUsingTemplate(Language language) + private static string GetUsingTemplate(Language language) { switch (language) { @@ -520,7 +528,7 @@ private string GetUsingTemplate(Language language) // Generate the code for the using statements private string GetUsingSet(Language language) { - StringBuilder usingNamespaceSet = new StringBuilder(); + StringBuilder usingNamespaceSet = new(); switch (language) { @@ -546,12 +554,37 @@ 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)) + { + 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 + if (OutputType != OutputAssemblyType.Library) { ThrowTerminatingError( new ErrorRecord( - new PSNotSupportedException(AddTypeStrings.CannotDefineNewType), "CannotDefineNewType", ErrorCategory.PermissionDenied, null)); + new PSNotSupportedException(AddTypeStrings.AssemblyTypeNotSupported), + nameof(AddTypeStrings.AssemblyTypeNotSupported), + ErrorCategory.NotImplemented, + targetObject: OutputType)); } } @@ -564,7 +597,7 @@ protected override void EndProcessing() // assembly type without an output assembly if (string.IsNullOrEmpty(_outputAssembly) && this.MyInvocation.BoundParameters.ContainsKey(nameof(OutputType))) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception( string.Format( CultureInfo.CurrentCulture, @@ -616,21 +649,21 @@ protected override void EndProcessing() private static readonly string s_frameworkFolder = PathType.GetDirectoryName(typeof(object).Assembly.Location); // These assemblies are always automatically added to ReferencedAssemblies. - private static readonly Lazy s_autoReferencedAssemblies = new Lazy(InitAutoIncludedRefAssemblies); + private static readonly Lazy s_autoReferencedAssemblies = new(InitAutoIncludedRefAssemblies); // A HashSet of assembly names to be ignored if they are specified in '-ReferencedAssemblies' - private static readonly Lazy> s_refAssemblyNamesToIgnore = new Lazy>(InitRefAssemblyNamesToIgnore); + private static readonly Lazy> s_refAssemblyNamesToIgnore = new(InitRefAssemblyNamesToIgnore); // These assemblies are used, when ReferencedAssemblies parameter is not specified. - private static readonly Lazy> s_defaultAssemblies = new Lazy>(InitDefaultRefAssemblies); + private static readonly Lazy> s_defaultAssemblies = new(InitDefaultRefAssemblies); private bool InMemory { get { return string.IsNullOrEmpty(_outputAssembly); } } // 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 HashSet(); - private static readonly Dictionary s_sourceAssemblyCache = new Dictionary(); + private static readonly ConcurrentDictionary s_sourceTypesCache = new(); + private static readonly ConcurrentDictionary s_sourceAssemblyCache = new(); private static readonly string s_defaultSdkDirectory = Utils.DefaultPowerShellAppBase; @@ -651,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) @@ -665,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)) { @@ -679,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; } @@ -826,7 +854,7 @@ private string ResolveAssemblyName(string assembly, bool isForReferenceAssembly) // However, this does give us a massive usability improvement, as users can just say // Add-Type -AssemblyName Forms (instead of System.Windows.Forms) // This is just long, not unmaintainable. - private Assembly LoadAssemblyHelper(string assemblyName) + private static Assembly LoadAssemblyHelper(string assemblyName) { Assembly loadedAssembly = null; @@ -852,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); @@ -877,25 +908,29 @@ 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 #region SourceCodeProcessing - private OutputKind OutputAssemblyTypeToOutputKind(OutputAssemblyType outputType) + private static OutputKind OutputAssemblyTypeToOutputKind(OutputAssemblyType outputType) { switch (outputType) { case OutputAssemblyType.Library: return OutputKind.DynamicallyLinkedLibrary; - case OutputAssemblyType.ConsoleApplication: - return OutputKind.ConsoleApplication; - case OutputAssemblyType.WindowsApplication: - return OutputKind.WindowsApplication; + default: - throw new ArgumentOutOfRangeException(nameof(outputType)); + throw PSTraceSource.NewNotSupportedException(); } } @@ -903,12 +938,11 @@ private CommandLineArguments ParseCompilerOption(IEnumerable args) { string sdkDirectory = s_defaultSdkDirectory; string baseDirectory = this.SessionState.Path.CurrentLocation.Path; - string additionalReferenceDirectories = null; switch (Language) { case Language.CSharp: - return CSharpCommandLineParser.Default.Parse(args, baseDirectory, sdkDirectory, additionalReferenceDirectories); + return CSharpCommandLineParser.Default.Parse(args, baseDirectory, sdkDirectory); default: throw PSTraceSource.NewNotSupportedException(); @@ -939,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."); @@ -985,7 +1019,7 @@ private void SourceCodeProcessing() } SourceText sourceText; - List syntaxTrees = new List(); + List syntaxTrees = new(); switch (ParameterSetName) { @@ -1024,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); } @@ -1064,12 +1098,12 @@ private void CompileToAssembly(List syntaxTrees, CompilationOptions private void CheckDuplicateTypes(Compilation compilation, out ConcurrentBag newTypes) { - AllNamedTypeSymbolsVisitor visitor = new AllNamedTypeSymbolsVisitor(); + AllNamedTypeSymbolsVisitor visitor = new(); visitor.Visit(compilation.Assembly.GlobalNamespace); foreach (var symbolName in visitor.DuplicateSymbols) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception( string.Format(AddTypeStrings.TypeAlreadyExists, symbolName)), "TYPE_ALREADY_EXISTS", @@ -1078,9 +1112,9 @@ private void CheckDuplicateTypes(Compilation compilation, out ConcurrentBag 0) + if (!visitor.DuplicateSymbols.IsEmpty) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(AddTypeStrings.CompilerErrors), "COMPILER_ERRORS", ErrorCategory.InvalidData, @@ -1094,10 +1128,10 @@ private void CheckDuplicateTypes(Compilation compilation, out ConcurrentBag DuplicateSymbols = new ConcurrentBag(); - public readonly ConcurrentBag UniqueSymbols = new ConcurrentBag(); + public readonly ConcurrentBag DuplicateSymbols = new(); + public readonly ConcurrentBag UniqueSymbols = new(); public override void VisitNamespace(INamespaceSymbol symbol) { @@ -1114,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); } @@ -1125,17 +1159,17 @@ public override void VisitNamedType(INamedTypeSymbol symbol) } } - private void CacheNewTypes(ConcurrentBag newTypes) + 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) @@ -1154,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); @@ -1233,7 +1265,7 @@ private void HandleCompilerErrors(ImmutableArray compilerDiagnostics } else { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new Exception(errorText), "SOURCE_CODE_ERROR", ErrorCategory.InvalidData, @@ -1245,7 +1277,7 @@ private void HandleCompilerErrors(ImmutableArray compilerDiagnostics if (IsError) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(AddTypeStrings.CompilerErrors), "COMPILER_ERRORS", ErrorCategory.InvalidData, @@ -1255,7 +1287,7 @@ private void HandleCompilerErrors(ImmutableArray compilerDiagnostics } } - private string BuildErrorMessage(Diagnostic diagnisticRecord) + private static string BuildErrorMessage(Diagnostic diagnisticRecord) { var location = diagnisticRecord.Location; @@ -1278,7 +1310,7 @@ private string BuildErrorMessage(Diagnostic diagnisticRecord) var errorLineString = textLines[errorLineNumber].ToString(); var errorPosition = lineSpan.StartLinePosition.Character; - StringBuilder sb = new StringBuilder(diagnisticMessage.Length + errorLineString.Length * 2 + 4); + StringBuilder sb = new(diagnisticMessage.Length + errorLineString.Length * 2 + 4); sb.AppendLine(diagnisticMessage); sb.AppendLine(errorLineString); @@ -1304,7 +1336,7 @@ private string BuildErrorMessage(Diagnostic diagnisticRecord) private static int SyntaxTreeArrayGetHashCode(IEnumerable sts) { // We use our extension method EnumerableExtensions.SequenceGetHashCode(). - List stHashes = new List(); + List stHashes = new(); foreach (var st in sts) { stHashes.Add(SyntaxTreeGetHashCode(st)); 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 5a1b944792d..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; /// /// @@ -72,7 +72,10 @@ public SwitchParameter ExcludeDifferent [Parameter] public SwitchParameter IncludeEqual { - get { return _includeEqual; } + get + { + return _includeEqual; + } set { @@ -101,10 +104,10 @@ public SwitchParameter PassThru private List _referenceEntries; private readonly List _referenceEntryBacklog - = new List(); + = new(); private readonly List _differenceEntryBacklog - = new List(); + = new(); private OrderByProperty _orderByProperty = null; private OrderByPropertyComparer _comparer = null; @@ -168,7 +171,7 @@ private void Process(OrderByPropertyEntry differenceEntry) // 2005/07/19 Switched order of referenceEntry and differenceEntry // so that we cast differenceEntry to the type of referenceEntry. if (referenceEntry != null && differenceEntry != null && - 0 == _comparer.Compare(referenceEntry, differenceEntry)) + _comparer.Compare(referenceEntry, differenceEntry) == 0) { EmitMatch(referenceEntry); return; @@ -208,7 +211,7 @@ private void Process(OrderByPropertyEntry differenceEntry) // Add differenceEntry to differenceEntryBacklog if (differenceEntry != null) { - if (0 < SyncWindow) + if (SyncWindow > 0) { while (_differenceEntryBacklog.Count >= SyncWindow) { @@ -234,7 +237,7 @@ private void Process(OrderByPropertyEntry differenceEntry) // Add referenceEntry to referenceEntryBacklog if (referenceEntry != null) { - if (0 < SyncWindow) + if (SyncWindow > 0) { while (_referenceEntryBacklog.Count >= SyncWindow) { @@ -256,7 +259,7 @@ private void InitComparer() if (_comparer != null) return; - List referenceObjectList = new List(ReferenceObject); + List referenceObjectList = new(ReferenceObject); _orderByProperty = new OrderByProperty( this, referenceObjectList, Property, true, _cultureInfo, CaseSensitive); Diagnostics.Assert(_orderByProperty.Comparer != null, "no comparer"); @@ -284,7 +287,7 @@ private OrderByPropertyEntry MatchAndRemove( { OrderByPropertyEntry listEntry = list[i]; Diagnostics.Assert(listEntry != null, "null listEntry " + i); - if (0 == _comparer.Compare(match, listEntry)) + if (_comparer.Compare(match, listEntry) == 0) { list.RemoveAt(i); return listEntry; @@ -325,9 +328,9 @@ private void Emit(OrderByPropertyEntry entry, string sideIndicator) else { mshobj = new PSObject(); - if (Property == null || 0 == Property.Length) + if (Property == null || Property.Length == 0) { - PSNoteProperty inputNote = new PSNoteProperty( + PSNoteProperty inputNote = new( InputObjectPropertyName, entry.inputObject); mshobj.Properties.Add(inputNote); } @@ -348,7 +351,7 @@ private void Emit(OrderByPropertyEntry entry, string sideIndicator) object prop = hash[FormatParameterDefinitionKeys.ExpressionEntryKey]; Diagnostics.Assert(prop != null, "null prop"); string propName = prop.ToString(); - PSNoteProperty propertyNote = new PSNoteProperty( + PSNoteProperty propertyNote = new( propName, entry.orderValues[i].PropertyValue); try @@ -364,7 +367,7 @@ private void Emit(OrderByPropertyEntry entry, string sideIndicator) } mshobj.Properties.Remove(SideIndicatorPropertyName); - PSNoteProperty sideNote = new PSNoteProperty( + PSNoteProperty sideNote = new( SideIndicatorPropertyName, sideIndicator); mshobj.Properties.Add(sideNote); WriteObject(mshobj); @@ -406,12 +409,12 @@ protected override void ProcessRecord() return; } - if (_comparer == null && 0 < DifferenceObject.Length) + if (_comparer == null && DifferenceObject.Length > 0) { InitComparer(); } - List differenceList = new List(DifferenceObject); + List differenceList = new(DifferenceObject); List differenceEntries = OrderByProperty.CreateOrderMatrix( this, differenceList, _orderByProperty.MshParameterList); @@ -459,7 +462,7 @@ private void HandleDifferenceObjectOnly() return; } - List differenceList = new List(DifferenceObject); + List differenceList = new(DifferenceObject); _orderByProperty = new OrderByProperty( this, differenceList, Property, true, _cultureInfo, CaseSensitive); List differenceEntries = diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConsoleColorCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConsoleColorCmdlet.cs index 250b00422d6..e4d36c66106 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConsoleColorCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConsoleColorCmdlet.cs @@ -10,12 +10,11 @@ namespace Microsoft.PowerShell.Commands /// /// Base class for a variety of commandlets that take color parameters. /// - public class ConsoleColorCmdlet : PSCmdlet { /// - /// Default ctor. + /// Initializes a new instance of the class. /// public ConsoleColorCmdlet() { @@ -26,7 +25,6 @@ public ConsoleColorCmdlet() /// The -ForegroundColor parameter. /// /// - [Parameter] public ConsoleColor @@ -60,7 +58,6 @@ public ConsoleColorCmdlet() /// /// /// - [Parameter] public ConsoleColor @@ -95,7 +92,7 @@ public ConsoleColorCmdlet() private static ErrorRecord BuildOutOfRangeErrorRecord(object val, string errorId) { string msg = StringUtil.Format(HostStrings.InvalidColorErrorTemplate, val.ToString()); - ArgumentOutOfRangeException e = new ArgumentOutOfRangeException("value", val, msg); + ArgumentOutOfRangeException e = new("value", val, msg); return new ErrorRecord(e, errorId, ErrorCategory.InvalidArgument, null); } #endregion helper @@ -109,4 +106,3 @@ private static ErrorRecord BuildOutOfRangeErrorRecord(object val, string errorId private readonly Type _consoleColorEnumType; } } - 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 1e64dfe7754..48bda12cd8a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-SddlString.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; using System.Management.Automation; using System.Security.AccessControl; using System.Security.Principal; @@ -33,7 +31,10 @@ public sealed class ConvertFromSddlStringCommand : PSCmdlet [Parameter] public AccessRightTypeNames Type { - get { return _type; } + get + { + return _type; + } set { @@ -45,7 +46,7 @@ public AccessRightTypeNames Type private AccessRightTypeNames _type; private bool _isTypeSet = false; - private string ConvertToNTAccount(SecurityIdentifier securityIdentifier) + private static string ConvertToNTAccount(SecurityIdentifier securityIdentifier) { try { @@ -57,11 +58,11 @@ private string ConvertToNTAccount(SecurityIdentifier securityIdentifier) } } - private List GetApplicableAccessRights(int accessMask, AccessRightTypeNames? typeName) + private static List GetApplicableAccessRights(int accessMask, AccessRightTypeNames? typeName) { - List typesToExamine = new List(); - List foundAccessRightNames = new List(); - HashSet foundAccessRightValues = new HashSet(); + List typesToExamine = new(); + List foundAccessRightNames = new(); + HashSet foundAccessRightValues = new(); if (typeName != null) { @@ -69,7 +70,7 @@ private List GetApplicableAccessRights(int accessMask, AccessRightTypeNa } else { - foreach (AccessRightTypeNames member in Enum.GetValues(typeof(AccessRightTypeNames))) + foreach (AccessRightTypeNames member in Enum.GetValues()) { typesToExamine.Add(GetRealAccessRightType(member)); } @@ -80,9 +81,8 @@ private List GetApplicableAccessRights(int accessMask, AccessRightTypeNa 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); @@ -95,7 +95,7 @@ private List GetApplicableAccessRights(int accessMask, AccessRightTypeNa return foundAccessRightNames; } - private Type GetRealAccessRightType(AccessRightTypeNames typeName) + private static Type GetRealAccessRightType(AccessRightTypeNames typeName) { switch (typeName) { @@ -116,17 +116,17 @@ private Type GetRealAccessRightType(AccessRightTypeNames typeName) } } - private string[] ConvertAccessControlListToStrings(CommonAcl acl, AccessRightTypeNames? typeName) + private static string[] ConvertAccessControlListToStrings(CommonAcl acl, AccessRightTypeNames? typeName) { if (acl == null || acl.Count == 0) { return Array.Empty(); } - List aceStringList = new List(acl.Count); + List aceStringList = new(acl.Count); foreach (CommonAce ace in acl) { - StringBuilder aceString = new StringBuilder(); + StringBuilder aceString = new(); string ntAccount = ConvertToNTAccount(ace.SecurityIdentifier); aceString.Append($"{ntAccount}: {ace.AceQualifier}"); 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 2daa22a4f69..9272e85c05d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFrom-StringData.cs @@ -47,7 +47,7 @@ public string StringData /// protected override void ProcessRecord() { - Hashtable result = new Hashtable(StringComparer.OrdinalIgnoreCase); + Hashtable result = new(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(_stringData)) { @@ -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); @@ -95,4 +93,3 @@ protected override void ProcessRecord() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index dfa6bd6c4b0..28d117f639f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; @@ -12,8 +11,6 @@ using Microsoft.PowerShell.MarkdownRender; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -109,7 +106,7 @@ protected override void ProcessRecord() else { string errorMessage = StringUtil.Format(ConvertMarkdownStrings.InvalidInputObjectType, baseObj.GetType()); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidDataException(errorMessage), "InvalidInputObject", ErrorCategory.InvalidData, @@ -153,7 +150,7 @@ private async Task ReadContentFromFile(string filePath) try { - using (StreamReader reader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) + using (StreamReader reader = new(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) { string mdContent = await reader.ReadToEndAsync(); return mdContent; @@ -192,7 +189,7 @@ private List ResolvePath(string path, bool isLiteral) { ProviderInfo provider = null; PSDriveInfo drive = null; - List resolvedPaths = new List(); + List resolvedPaths = new(); try { @@ -219,7 +216,7 @@ private List ResolvePath(string path, bool isLiteral) if (!provider.Name.Equals("FileSystem", StringComparison.OrdinalIgnoreCase)) { string errorMessage = StringUtil.Format(ConvertMarkdownStrings.FileSystemPathsOnly, path); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(), "OnlyFileSystemPathsSupported", ErrorCategory.InvalidArgument, 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 b0c3ff27f35..41dd159a3ba 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; using System.Management.Automation.Internal; @@ -18,9 +17,9 @@ namespace Microsoft.PowerShell.Commands /// /// Class comment. /// - [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 { @@ -322,7 +321,7 @@ internal static class ConvertHTMLParameterDefinitionKeys /// /// This allows for @{e='foo';label='bar';alignment='center';width='20'}. /// - internal class ConvertHTMLExpressionParameterDefinition : CommandParameterDefinition + internal sealed class ConvertHTMLExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -342,13 +341,10 @@ protected override void SetEntries() /// private List ProcessParameter(object[] properties) { - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); + TerminatingErrorContext invocationContext = new(this); ParameterProcessor processor = - new ParameterProcessor(new ConvertHTMLExpressionParameterDefinition()); - if (properties == null) - { - properties = new object[] { "*" }; - } + new(new ConvertHTMLExpressionParameterDefinition()); + properties ??= new object[] { "*" }; return processor.ProcessParameters(properties, invocationContext); } @@ -396,7 +392,7 @@ private static Hashtable CreateAuxPropertyHT( string alignment, string width) { - Hashtable ht = new Hashtable(); + Hashtable ht = new(); if (label != null) { ht.Add(ConvertHTMLParameterDefinitionKeys.LabelEntryKey, label); @@ -446,8 +442,8 @@ protected override void BeginProcessing() // ValidateNotNullOrEmpty attribute is not working for System.Uri datatype, so handling it here if ((_cssuriSpecified) && (string.IsNullOrEmpty(_cssuri.OriginalString.Trim()))) { - ArgumentException ex = new ArgumentException(StringUtil.Format(UtilityCommonStrings.EmptyCSSUri, "CSSUri")); - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, "CSSUri"); + ArgumentException ex = new(StringUtil.Format(UtilityCommonStrings.EmptyCSSUri, "CSSUri")); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.InvalidArgument, "CSSUri"); ThrowTerminatingError(er); } @@ -482,7 +478,7 @@ protected override void BeginProcessing() if (_metaSpecified) { - List useditems = new List(); + List useditems = new(); foreach (string s in _meta.Keys) { if (!useditems.Contains(s)) @@ -505,10 +501,9 @@ protected override void BeginProcessing() default: MshCommandRuntime mshCommandRuntime = this.CommandRuntime as MshCommandRuntime; string Message = StringUtil.Format(ConvertHTMLStrings.MetaPropertyNotFound, s, _meta[s]); - WarningRecord record = new WarningRecord(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; + WarningRecord record = new(Message); - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -551,26 +546,24 @@ protected override void BeginProcessing() /// private void WriteColumns(List mshParams) { - StringBuilder COLTag = new StringBuilder(); + StringBuilder COLTag = new(); COLTag.Append(""); foreach (MshParameter p in mshParams) { COLTag.Append(""); @@ -589,12 +582,12 @@ private void WriteListEntry() { foreach (MshParameter p in _resolvedNameMshParameters) { - StringBuilder Listtag = new StringBuilder(); + StringBuilder Listtag = new(); Listtag.Append(""); // for writing the property name WritePropertyName(Listtag, p); - Listtag.Append(":"); + Listtag.Append(':'); Listtag.Append(""); // for writing the property value @@ -609,11 +602,10 @@ private void WriteListEntry() /// /// To write the Property name. /// - private void WritePropertyName(StringBuilder Listtag, MshParameter p) + private static void WritePropertyName(StringBuilder Listtag, MshParameter p) { // for writing the property name - string label = p.GetEntry(ConvertHTMLParameterDefinitionKeys.LabelEntryKey) as string; - if (label != null) + if (p.GetEntry(ConvertHTMLParameterDefinitionKeys.LabelEntryKey) is string label) { Listtag.Append(label); } @@ -654,7 +646,7 @@ private void WritePropertyValue(StringBuilder Listtag, MshParameter p) /// /// To write the Table header for the object property names. /// - private void WriteTableHeader(StringBuilder THtag, List resolvedNameMshParameters) + private static void WriteTableHeader(StringBuilder THtag, List resolvedNameMshParameters) { // write the property names foreach (MshParameter p in resolvedNameMshParameters) @@ -715,7 +707,7 @@ protected override void ProcessRecord() { WriteColumns(_resolvedNameMshParameters); - StringBuilder THtag = new StringBuilder(""); + StringBuilder THtag = new(""); // write the table header WriteTableHeader(THtag, _resolvedNameMshParameters); @@ -728,7 +720,7 @@ protected override void ProcessRecord() // if the As parameter is Table, write the property values if (_as.Equals("Table", StringComparison.OrdinalIgnoreCase)) { - StringBuilder TRtag = new StringBuilder(""); + StringBuilder TRtag = new(""); // write the table row WriteTableRow(TRtag, _resolvedNameMshParameters); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs index 3bb626ca06e..d0b9f91e7f7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.Commands /// /// This class is used to parse CSV text. /// - internal class CSVHelper + internal sealed class CSVHelper { internal CSVHelper(char delimiter) { @@ -28,7 +28,7 @@ internal CSVHelper(char delimiter) /// internal Collection ParseCsv(string csv) { - Collection result = new Collection(); + Collection result = new(); string tempString = string.Empty; csv = csv.Trim(); if (csv.Length == 0 || csv[0] == '#') diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index 0372298d7b5..cb455417531 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; @@ -11,8 +12,6 @@ using System.Management.Automation; using System.Text; -using Dbg = System.Management.Automation.Diagnostics; - #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings namespace Microsoft.PowerShell.Commands @@ -33,9 +32,9 @@ public abstract class BaseCsvWritingCommand : PSCmdlet [ValidateNotNull] public char Delimiter { get; set; } - /// - ///Culture switch for csv conversion - /// + /// + /// Culture switch for csv conversion + /// [Parameter(ParameterSetName = "UseCulture")] public SwitchParameter UseCulture { get; set; } @@ -43,21 +42,22 @@ public abstract class BaseCsvWritingCommand : PSCmdlet /// Abstract Property - Input Object which is written in Csv format. /// Derived as Different Attributes.In ConvertTo-CSV, This is a positional parameter. Export-CSV not a Positional behaviour. /// - public abstract PSObject InputObject { get; set; } /// - /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. Cannot specify with NoTypeInformation. + /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. /// [Parameter] [Alias("ITI")] public SwitchParameter IncludeTypeInformation { get; set; } /// - /// NoTypeInformation : The #TYPE line should not be generated. Default is true. Cannot specify with IncludeTypeInformation. + /// Gets or sets a value indicating whether to suppress the #TYPE line. + /// This parameter is obsolete and has no effect. It is retained for backward compatibility only. /// [Parameter(DontShow = true)] [Alias("NTI")] + [Obsolete("This parameter is obsolete and has no effect. The default behavior is to not include type information. Use -IncludeTypeInformation to include type information.")] public SwitchParameter NoTypeInformation { get; set; } = true; /// @@ -74,6 +74,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 /// @@ -111,23 +117,11 @@ protected override void BeginProcessing() { if (this.MyInvocation.BoundParameters.ContainsKey(nameof(QuoteFields)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(UseQuotes))) { - InvalidOperationException exception = new InvalidOperationException(CsvCommandStrings.CannotSpecifyQuoteFieldsAndUseQuotes); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyQuoteFieldsAndUseQuotes", ErrorCategory.InvalidData, null); - this.ThrowTerminatingError(errorRecord); - } - - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(NoTypeInformation))) - { - InvalidOperationException exception = new InvalidOperationException(CsvCommandStrings.CannotSpecifyIncludeTypeInformationAndNoTypeInformation); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyIncludeTypeInformationAndNoTypeInformation", ErrorCategory.InvalidData, null); + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyQuoteFieldsAndUseQuotes); + ErrorRecord errorRecord = new(exception, "CannotSpecifyQuoteFieldsAndUseQuotes", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(errorRecord); } - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation))) - { - NoTypeInformation = !IncludeTypeInformation; - } - Delimiter = ImportExportCSVHelper.SetDelimiter(this, ParameterSetName, Delimiter, UseCulture); } } @@ -214,10 +208,24 @@ public string LiteralPath /// Gets or sets encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; /// /// Gets or sets property that sets append parameter. @@ -247,8 +255,16 @@ protected override void BeginProcessing() // Validate that they don't provide both Path and LiteralPath, but have provided at least one. if (!(_specifiedPath ^ _isLiteralPath)) { - InvalidOperationException exception = new InvalidOperationException(CsvCommandStrings.CannotSpecifyPathAndLiteralPath); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyPathAndLiteralPath", ErrorCategory.InvalidData, null); + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyPathAndLiteralPath); + ErrorRecord errorRecord = new(exception, "CannotSpecifyPathAndLiteralPath", ErrorCategory.InvalidData, null); + this.ThrowTerminatingError(errorRecord); + } + + // Validate that Append and NoHeader are not specified together. + if (Append && NoHeader) + { + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyAppendAndNoHeader); + ErrorRecord errorRecord = new(exception, "CannotSpecifyBothAppendAndNoHeader", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(errorRecord); } @@ -289,9 +305,9 @@ protected override void ProcessRecord() } // write headers (row1: typename + row2: column names) - if (!_isActuallyAppending) + if (!_isActuallyAppending && !NoHeader.IsPresent) { - if (NoTypeInformation == false) + if (IncludeTypeInformation) { WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); } @@ -302,7 +318,6 @@ protected override void ProcessRecord() string csv = _helper.ConvertPSObjectToCSV(InputObject, _propertyNames); WriteCsvLine(csv); - _sw.Flush(); } /// @@ -347,7 +362,7 @@ private void CreateFileStream() { using (StreamReader streamReader = PathUtils.OpenStreamReader(this, this.Path, Encoding, _isLiteralPath)) { - isCsvFileEmpty = streamReader.Peek() == -1 ? true : false; + isCsvFileEmpty = streamReader.Peek() == -1; } } @@ -360,7 +375,7 @@ private void CreateFileStream() using (StreamReader streamReader = PathUtils.OpenStreamReader(this, this.Path, Encoding, _isLiteralPath)) { - ImportCsvHelper readingHelper = new ImportCsvHelper( + ImportCsvHelper readingHelper = new( this, this.Delimiter, null /* header */, null /* typeName */, streamReader); readingHelper.ReadHeader(); _preexistingPropertyNames = readingHelper.Header; @@ -404,7 +419,6 @@ private void CleanUp() { if (_sw != null) { - _sw.Flush(); _sw.Dispose(); _sw = null; } @@ -417,10 +431,7 @@ private void CleanUp() _readOnlyFileInfo.Attributes |= FileAttributes.ReadOnly; } - if (_helper != null) - { - _helper.Dispose(); - } + _helper?.Dispose(); } private void ReconcilePreexistingPropertyNames() @@ -435,7 +446,7 @@ private void ReconcilePreexistingPropertyNames() throw new InvalidOperationException(CsvCommandStrings.ReconcilePreexistingPropertyNamesMethodShouldOnlyGetCalledWhenPreexistingPropertyNamesHaveBeenReadSuccessfully); } - HashSet appendedPropertyNames = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet appendedPropertyNames = new(StringComparer.OrdinalIgnoreCase); foreach (string appendedPropertyName in _propertyNames) { appendedPropertyNames.Add(appendedPropertyName); @@ -452,8 +463,8 @@ private void ReconcilePreexistingPropertyNames() CsvCommandStrings.CannotAppendCsvWithMismatchedPropertyNames, preexistingPropertyName, this.Path); - InvalidOperationException exception = new InvalidOperationException(errorMessage); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotAppendCsvWithMismatchedPropertyNames", ErrorCategory.InvalidData, preexistingPropertyName); + InvalidOperationException exception = new(errorMessage); + ErrorRecord errorRecord = new(exception, "CannotAppendCsvWithMismatchedPropertyNames", ErrorCategory.InvalidData, preexistingPropertyName); this.ThrowTerminatingError(errorRecord); } } @@ -491,7 +502,7 @@ public override void WriteCsvLine(string line) /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } @@ -577,9 +588,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")] @@ -589,10 +600,24 @@ public string[] LiteralPath /// Gets or sets encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; /// /// Avoid writing out duplicate warning messages when there are one or more unspecified names. @@ -619,8 +644,8 @@ protected override void ProcessRecord() // Validate that they don't provide both Path and LiteralPath, but have provided at least one. if (!(_specifiedPath ^ _isLiteralPath)) { - InvalidOperationException exception = new InvalidOperationException(CsvCommandStrings.CannotSpecifyPathAndLiteralPath); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyPathAndLiteralPath", ErrorCategory.InvalidData, null); + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyPathAndLiteralPath); + ErrorRecord errorRecord = new(exception, "CannotSpecifyPathAndLiteralPath", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(errorRecord); } @@ -630,7 +655,7 @@ protected override void ProcessRecord() { using (StreamReader streamReader = PathUtils.OpenStreamReader(this, path, this.Encoding, _isLiteralPath)) { - ImportCsvHelper helper = new ImportCsvHelper(this, Delimiter, Header, null /* typeName */, streamReader); + ImportCsvHelper helper = new(this, Delimiter, Header, null /* typeName */, streamReader); try { @@ -638,7 +663,7 @@ protected override void ProcessRecord() } catch (ExtendedTypeSystemException exception) { - ErrorRecord errorRecord = new ErrorRecord(exception, "AlreadyPresentPSMemberInfoInternalCollectionAdd", ErrorCategory.NotSpecified, null); + ErrorRecord errorRecord = new(exception, "AlreadyPresentPSMemberInfoInternalCollectionAdd", ErrorCategory.NotSpecified, null); this.ThrowTerminatingError(errorRecord); } } @@ -704,22 +729,30 @@ protected override void ProcessRecord() if (_propertyNames == null) { _propertyNames = ExportCsvHelper.BuildPropertyNames(InputObject, _propertyNames); - if (NoTypeInformation == false) + + if (!NoHeader.IsPresent) { - WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); - } + if (IncludeTypeInformation) + { + 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 @@ -758,9 +791,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] @@ -775,9 +808,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] @@ -808,10 +841,10 @@ protected override void ProcessRecord() { foreach (PSObject inputObject in InputObject) { - using (MemoryStream memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(inputObject.ToString()))) - using (StreamReader streamReader = new StreamReader(memoryStream, System.Text.Encoding.Unicode)) + using (MemoryStream memoryStream = new(Encoding.Unicode.GetBytes(inputObject.ToString()))) + using (StreamReader streamReader = new(memoryStream, System.Text.Encoding.Unicode)) { - ImportCsvHelper helper = new ImportCsvHelper(this, Delimiter, Header, _typeName, streamReader); + ImportCsvHelper helper = new(this, Delimiter, Header, _typeName, streamReader); try { @@ -819,7 +852,7 @@ protected override void ProcessRecord() } catch (ExtendedTypeSystemException exception) { - ErrorRecord errorRecord = new ErrorRecord(exception, "AlreadyPresentPSMemberInfoInternalCollectionAdd", ErrorCategory.NotSpecified, null); + ErrorRecord errorRecord = new(exception, "AlreadyPresentPSMemberInfoInternalCollectionAdd", ErrorCategory.NotSpecified, null); this.ThrowTerminatingError(errorRecord); } @@ -850,15 +883,15 @@ protected override void ProcessRecord() /// /// Helper class for Export-Csv and ConvertTo-Csv. /// - internal class ExportCsvHelper : IDisposable + internal sealed class ExportCsvHelper : IDisposable { - private char _delimiter; + private readonly char _delimiter; private readonly BaseCsvWritingCommand.QuoteKind _quoteKind; private readonly HashSet _quoteFields; private readonly StringBuilder _outputString; /// - /// Create ExportCsvHelper instance. + /// Initializes a new instance of the class. /// /// Delimiter char. /// Kind of quoting. @@ -883,16 +916,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; @@ -904,10 +957,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; @@ -942,7 +992,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); } @@ -970,10 +1021,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; @@ -989,11 +1037,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 _)) @@ -1013,7 +1076,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); } @@ -1044,10 +1107,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 @@ -1097,7 +1157,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; @@ -1144,7 +1204,7 @@ internal static void AppendStringWithEscapeAlways(StringBuilder dest, string sou /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { GC.SuppressFinalize(this); } @@ -1162,7 +1222,7 @@ public void Dispose() /// /// Helper class to import single CSV file. /// - internal class ImportCsvHelper + internal sealed class ImportCsvHelper { #region constructor @@ -1209,15 +1269,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; @@ -1359,16 +1412,12 @@ private static void ValidatePropertyNames(IList names) } else { - HashSet headers = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet headers = new(StringComparer.OrdinalIgnoreCase); foreach (string currentHeader in 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 = @@ -1377,7 +1426,7 @@ private static void ValidatePropertyNames(IList names) ExtendedTypeSystem.MemberAlreadyPresent, currentHeader); - ExtendedTypeSystemException exception = new ExtendedTypeSystemException(memberAlreadyPresentMsg); + ExtendedTypeSystemException exception = new(memberAlreadyPresentMsg); throw exception; } } @@ -1611,7 +1660,7 @@ private bool IsNewLine(char ch, out string newLine) /// private void ReadTillNextDelimiter(StringBuilder current, ref bool endOfRecord, bool eatTrailingBlanks) { - StringBuilder temp = new StringBuilder(); + StringBuilder temp = new(); // Did we see any non-whitespace character bool nonWhiteSpace = false; @@ -1659,7 +1708,7 @@ private void ReadTillNextDelimiter(StringBuilder current, ref bool endOfRecord, private PSObject BuildMshobject(string type, IList names, List values, char delimiter, bool preValidated = false) { - PSObject result = new PSObject(names.Count); + PSObject result = new(names.Count); char delimiterlocal = delimiter; int unspecifiedNameIndex = 1; for (int i = 0; i <= names.Count - 1; i++) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs index 465ef91f037..6d224d29007 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Collections.Generic; using System.Management.Automation.Internal; @@ -15,23 +14,23 @@ namespace System.Management.Automation /// /// This class provides functionality for serializing a PSObject. /// - internal class CustomSerialization + internal sealed class CustomSerialization { #region constructor /// /// Depth of serialization. /// - private int _depth; + private readonly int _depth; /// /// XmlWriter to be used for writing. /// - private XmlWriter _writer; + private readonly XmlWriter _writer; /// /// Whether type information should be included in the xml. /// - private bool _notypeinformation; + private readonly bool _notypeinformation; /// /// CustomerSerializer used for formatting the output for _writer. @@ -39,7 +38,7 @@ internal class CustomSerialization private CustomInternalSerializer _serializer; /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// writer to be used for serialization. @@ -75,7 +74,7 @@ internal CustomSerialization(XmlWriter writer, bool notypeinformation, int depth public static int MshDefaultSerializationDepth { get; } = 1; /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// writer to be used for serialization. @@ -171,10 +170,7 @@ internal void DoneAsStream() internal void Stop() { CustomInternalSerializer serializer = _serializer; - if (serializer != null) - { - serializer.Stop(); - } + serializer?.Stop(); } #endregion @@ -183,15 +179,14 @@ internal void Stop() /// /// This internal helper class provides methods for serializing mshObject. /// - internal class - CustomInternalSerializer + internal sealed class CustomInternalSerializer { #region constructor /// /// Xml writer to be used. /// - private XmlWriter _writer; + private readonly XmlWriter _writer; /// /// Check first call for every pipeline object to write Object tag else property tag. @@ -201,7 +196,7 @@ internal class /// /// Should the type information to be shown. /// - private bool _notypeinformation; + private readonly bool _notypeinformation; /// /// Check object call. @@ -209,7 +204,7 @@ internal class private bool _firstobjectcall = true; /// - /// Constructor. + /// Initializes a new instance of the class. /// /// /// Xml writer to be used. @@ -342,8 +337,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; @@ -442,7 +436,7 @@ private bool HandleKnownContainerTypes(object source, string property, int depth /// /// /// - private void GetKnownContainerTypeInfo( + private static void GetKnownContainerTypeInfo( object source, out ContainerType ct, out IDictionary dictionary, out IEnumerable enumerable) { Dbg.Assert(source != null, "caller should validate the parameter"); @@ -676,7 +670,7 @@ private void WriteStartOfPSObject( /// /// /// - private bool PSObjectHasNotes(PSObject source) + private static bool PSObjectHasNotes(PSObject source) { if (source.InstanceMembers != null && source.InstanceMembers.Count > 0) { @@ -710,8 +704,7 @@ private void WriteMemberInfoCollection( continue; } - PSPropertyInfo property = info as PSPropertyInfo; - if (property == null) + if (info is not PSPropertyInfo property) { continue; } @@ -743,7 +736,7 @@ private void WritePSObjectProperties(PSObject source, int depth) Dbg.Assert(depth >= 0, "depth should be greater or equal to zero"); if (source.GetSerializationMethod(null) == SerializationMethod.SpecificProperties) { - PSMemberInfoInternalCollection specificProperties = new PSMemberInfoInternalCollection(); + PSMemberInfoInternalCollection specificProperties = new(); foreach (string propertyName in source.GetSpecificPropertiesToSerialize(null)) { PSPropertyInfo property = source.Properties[propertyName]; @@ -956,7 +949,7 @@ private void HandlePSObjectAsString(PSObject source, string property, int depth) /// /// string value to use for serializing this PSObject. /// - private string GetStringFromPSObject(PSObject source) + private static string GetStringFromPSObject(PSObject source) { Dbg.Assert(source != null, "caller should have validated the information"); @@ -1104,7 +1097,6 @@ private void WriteObjectString( /// Name of property. Pass null for item. /// Object to be written. /// Serialization information about source. - private void WriteOnePrimitiveKnownType( XmlWriter writer, string property, object source, TypeSerializationInfo entry) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index 355d94912af..756afff13de 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -43,7 +43,7 @@ public sealed class DebugRunspaceCommand : PSCmdlet // Debugging to persist until Ctrl+C or Debugger 'Exit' stops cmdlet. private bool _debugging; - private ManualResetEventSlim _newRunningScriptEvent = new ManualResetEventSlim(true); + private readonly ManualResetEventSlim _newRunningScriptEvent = new(true); private RunspaceAvailability _previousRunspaceAvailability = RunspaceAvailability.None; #endregion @@ -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(); @@ -269,12 +265,24 @@ private void WaitAndReceiveRunspaceOutput() // Set up host script debugger to debug the runspace. _debugger.DebugRunspace(_runspace, breakAll: BreakAll); + _runspace.IsRemoteDebuggerAttached = true; + _runspace.Events?.GenerateEvent( + PSEngineEvent.OnDebugAttach, + sender: null, + args: Array.Empty(), + extraData: null, + processInCurrentThread: true, + waitForCompletionInCurrentThread: false); + while (_debugging) { // Wait for running script. _newRunningScriptEvent.Wait(); - if (!_debugging) { return; } + if (!_debugging) + { + return; + } AddDataEventHandlers(); @@ -308,6 +316,7 @@ private void WaitAndReceiveRunspaceOutput() { _runspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged; _debugger.NestedDebuggingCancelledEvent -= HandleDebuggerNestedDebuggingCancelledEvent; + _runspace.IsRemoteDebuggerAttached = false; _debugger.StopDebugRunspace(_runspace); _newRunningScriptEvent.Dispose(); } @@ -335,9 +344,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 +417,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 +447,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 +455,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 +513,10 @@ private void HandlePowerShellPStreamItem(PSStreamObject streamItem) private void AddToDebugBlockingCollection(PSStreamObject streamItem) { - if (!_debugBlockingCollection.IsOpen) { return; } + if (!_debugBlockingCollection.IsOpen) + { + return; + } if (streamItem != null) { @@ -537,8 +545,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 { @@ -549,10 +556,9 @@ private void EnableHostDebugger(Runspace runspace, bool enabled) } } - private void SetLocalMode(System.Management.Automation.Debugger debugger, bool localMode) + 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/Disable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs index c54c9499761..1bdd5a5aea8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Disable-PSBreakpoint.cs @@ -8,41 +8,36 @@ namespace Microsoft.PowerShell.Commands /// /// This class implements Disable-PSBreakpoint. /// - [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096498")] + [Cmdlet(VerbsLifecycle.Disable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096498")] [OutputType(typeof(Breakpoint))] - public class DisablePSBreakpointCommand : PSBreakpointCommandBase + public class DisablePSBreakpointCommand : PSBreakpointUpdaterCommandBase { + #region parameters + /// /// Gets or sets the parameter -passThru which states whether the /// command should place the breakpoints it processes in the pipeline. /// [Parameter] - public SwitchParameter PassThru - { - get - { - return _passThru; - } + public SwitchParameter PassThru { get; set; } - set - { - _passThru = value; - } - } + #endregion parameters - private bool _passThru; + #region overrides /// /// Disables the given breakpoint. /// protected override void ProcessBreakpoint(Breakpoint breakpoint) { - this.Context.Debugger.DisableBreakpoint(breakpoint); + breakpoint = Runspace.Debugger.DisableBreakpoint(breakpoint); - if (_passThru) + if (PassThru) { - WriteObject(breakpoint); + base.ProcessBreakpoint(breakpoint); } } + + #endregion overrides } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs index d06073c4214..0822e146ae6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Enable-PSBreakpoint.cs @@ -1,158 +1,43 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Diagnostics; using System.Management.Automation; -using System.Management.Automation.Internal; namespace Microsoft.PowerShell.Commands { - /// - /// Base class for Enable/Disable/Remove-PSBreakpoint. - /// - public abstract class PSBreakpointCommandBase : PSCmdlet - { - /// - /// The breakpoint to enable. - /// - [Parameter(ParameterSetName = "Breakpoint", ValueFromPipeline = true, Position = 0, Mandatory = true)] - [ValidateNotNull] - public Breakpoint[] Breakpoint - { - get - { - return _breakpoints; - } - - set - { - _breakpoints = value; - } - } - - private Breakpoint[] _breakpoints; - - /// - /// The Id of the breakpoint to enable. - /// - [Parameter(ParameterSetName = "Id", ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)] - [ValidateNotNull] - public int[] Id - { - get - { - return _ids; - } - - set - { - _ids = value; - } - } - - private int[] _ids; - - /// - /// Gathers the list of breakpoints to process and calls ProcessBreakpoints. - /// - protected override void ProcessRecord() - { - if (ParameterSetName.Equals("Breakpoint", StringComparison.OrdinalIgnoreCase)) - { - foreach (Breakpoint breakpoint in _breakpoints) - { - if (ShouldProcessInternal(breakpoint.ToString())) - { - ProcessBreakpoint(breakpoint); - } - } - } - else - { - Debug.Assert(ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase)); - - foreach (int i in _ids) - { - Breakpoint breakpoint = this.Context.Debugger.GetBreakpoint(i); - - if (breakpoint == null) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.BreakpointIdNotFound, i)), - "PSBreakpoint:BreakpointIdNotFound", - ErrorCategory.InvalidArgument, - null)); - continue; - } - - if (ShouldProcessInternal(breakpoint.ToString())) - { - ProcessBreakpoint(breakpoint); - } - } - } - } - - /// - /// Process the given breakpoint. - /// - protected abstract void ProcessBreakpoint(Breakpoint breakpoint); - - private bool ShouldProcessInternal(string target) - { - // ShouldProcess should be called only if the WhatIf or Confirm parameters are passed in explicitly. - // It should *not* be called if we are in a nested debug prompt and the current running command was - // run with -WhatIf or -Confirm, because this prevents the user from adding/removing breakpoints inside - // a debugger stop. - if (this.MyInvocation.BoundParameters.ContainsKey("WhatIf") || this.MyInvocation.BoundParameters.ContainsKey("Confirm")) - { - return ShouldProcess(target); - } - - return true; - } - } - /// /// This class implements Enable-PSBreakpoint. /// - [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Id", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096700")] + [Cmdlet(VerbsLifecycle.Enable, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096700")] [OutputType(typeof(Breakpoint))] - public class EnablePSBreakpointCommand : PSBreakpointCommandBase + public class EnablePSBreakpointCommand : PSBreakpointUpdaterCommandBase { + #region parameters + /// /// Gets or sets the parameter -passThru which states whether the /// command should place the breakpoints it processes in the pipeline. /// [Parameter] - public SwitchParameter PassThru - { - get - { - return _passThru; - } + public SwitchParameter PassThru { get; set; } - set - { - _passThru = value; - } - } + #endregion parameters - private bool _passThru; + #region overrides /// /// Enables the given breakpoint. /// protected override void ProcessBreakpoint(Breakpoint breakpoint) { - this.Context.Debugger.EnableBreakpoint(breakpoint); + breakpoint = Runspace.Debugger.EnableBreakpoint(breakpoint); - if (_passThru) + if (PassThru) { - WriteObject(breakpoint); + base.ProcessBreakpoint(breakpoint); } } + + #endregion overrides } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs index c1294a08535..47b83c78770 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/EnableDisableRunspaceDebugCommand.cs @@ -26,11 +26,7 @@ public sealed class PSRunspaceDebug /// debugger is currently attached. The script or command will remain stopped until /// a debugger is attached to debug the breakpoint. /// - public bool Enabled - { - get; - private set; - } + public bool Enabled { get; } /// /// When true this property will cause any running command or script in the Runspace @@ -38,36 +34,24 @@ public bool Enabled /// script or command will remain stopped until a debugger is attached to debug the /// current stop point. /// - public bool BreakAll - { - get; - private set; - } + public bool BreakAll { get; } /// /// Name of runspace for which the options apply. /// - public string RunspaceName - { - get; - private set; - } + public string RunspaceName { get; } /// /// Local Id of runspace for which the options apply. /// - public int RunspaceId - { - get; - private set; - } + public int RunspaceId { get; } #endregion #region Constructors /// - /// Constructor. + /// Initializes a new instance of the class. /// /// Enable debugger option. /// BreakAll option. @@ -75,7 +59,10 @@ public int RunspaceId /// 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; @@ -131,7 +118,7 @@ public abstract class CommonRunspaceCommandBase : PSCmdlet /// [Parameter(Position = 0, ParameterSetName = CommonRunspaceCommandBase.RunspaceNameParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] RunspaceName { @@ -147,7 +134,7 @@ public string[] RunspaceName ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = CommonRunspaceCommandBase.RunspaceParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public Runspace[] Runspace { @@ -161,7 +148,7 @@ public Runspace[] Runspace [Parameter(Position = 0, Mandatory = true, ParameterSetName = CommonRunspaceCommandBase.RunspaceIdParameterSet)] - [ValidateNotNullOrEmpty()] + [ValidateNotNullOrEmpty] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] RunspaceId { @@ -174,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 { @@ -186,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; @@ -197,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 @@ -291,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()); } @@ -307,7 +291,7 @@ protected void SetDebugPreferenceHelper(string processName, string[] appDomainNa } catch (Exception ex) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new PSInvalidOperationException(string.Format(CultureInfo.InvariantCulture, Debugger.PersistDebugPreferenceFailure, processName), ex), fullyQualifiedErrorId, ErrorCategory.InvalidOperation, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ExportAliasCommand.cs index 1ac3e781ab7..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 } @@ -55,7 +55,10 @@ public string Path [Alias("PSPath", "LP")] public string LiteralPath { - get { return _path; } + get + { + return _path; + } set { @@ -114,7 +117,7 @@ public SwitchParameter PassThru /// /// Property that sets append parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Append { get @@ -133,7 +136,7 @@ public SwitchParameter Append /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -152,7 +155,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -181,6 +184,7 @@ public SwitchParameter NoClobber /// which scope the aliases are retrieved from. /// [Parameter] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion Parameters @@ -244,7 +248,7 @@ protected override void ProcessRecord() // that doesn't exist and they are not globbing. ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( aliasName, "AliasNotFound", SessionStateStrings.AliasNotFound); @@ -288,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) { @@ -299,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; @@ -310,7 +312,7 @@ protected override void EndProcessing() /// /// Holds all the matching aliases for writing to the file. /// - private Collection _matchingAliases = new Collection(); + private readonly Collection _matchingAliases = new(); private static string GetAliasLine(AliasInfo alias, string formatString) { @@ -406,7 +408,7 @@ private void ThrowFileOpenError(Exception e, string pathWithError) { string message = StringUtil.Format(AliasCommandStrings.ExportAliasFileOpenFailed, pathWithError, e.Message); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( e, "FileOpenFailure", ErrorCategory.OpenError, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs index 0d2db93302a..65efee78a28 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs @@ -48,8 +48,7 @@ internal Type GetValueType(PSObject liveObject, out object columnValue) /// The source string limited in the number of lines. internal static object LimitString(object src) { - string srcString = src as string; - if (srcString == null) + if (src is not string srcString) { return src; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs index a9d60def958..4d0c8af875c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs @@ -1,17 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Management.Automation; -using Microsoft.PowerShell.Commands.Internal.Format; - namespace Microsoft.PowerShell.Commands { - internal class ExpressionColumnInfo : ColumnInfo + internal sealed class ExpressionColumnInfo : ColumnInfo { - private PSPropertyExpression _expression; + private readonly PSPropertyExpression _expression; internal ExpressionColumnInfo(string staleObjectPropertyName, string displayName, PSPropertyExpression expression) : base(staleObjectPropertyName, displayName) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs index d7d863e29b8..4f4e84a1569 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs @@ -7,9 +7,9 @@ namespace Microsoft.PowerShell.Commands { - internal class HeaderInfo + internal sealed class HeaderInfo { - private List _columns = new List(); + private readonly List _columns = new(); internal void AddColumn(ColumnInfo col) { @@ -18,7 +18,7 @@ internal void AddColumn(ColumnInfo col) internal PSObject AddColumnsToWindow(OutWindowProxy windowProxy, PSObject liveObject) { - PSObject staleObject = new PSObject(); + PSObject staleObject = new(); // Initiate arrays to be of the same size. int count = _columns.Count; @@ -47,7 +47,7 @@ internal PSObject AddColumnsToWindow(OutWindowProxy windowProxy, PSObject liveOb internal PSObject CreateStalePSObject(PSObject liveObject) { - PSObject staleObject = new PSObject(); + PSObject staleObject = new(); foreach (ColumnInfo column in _columns) { // Add a property to the stale PSObject. 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 48a9004e604..4ec5e2240fa 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs @@ -9,10 +9,10 @@ namespace Microsoft.PowerShell.Commands { - internal class OriginalColumnInfo : ColumnInfo + internal sealed class OriginalColumnInfo : ColumnInfo { - private string _liveObjectPropertyName; - private OutGridViewCommand _parentCmdlet; + private readonly string _liveObjectPropertyName; + private readonly OutGridViewCommand _parentCmdlet; internal OriginalColumnInfo(string staleObjectPropertyName, string displayName, string liveObjectPropertyName, OutGridViewCommand parentCmdlet) : base(staleObjectPropertyName, displayName) @@ -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 b95e9968c5c..574ca39426d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs @@ -54,7 +54,7 @@ public class OutGridViewCommand : PSCmdlet, IDisposable #region Constructors /// - /// Constructor for OutGridView. + /// Initializes a new instance of the class. /// public OutGridViewCommand() { @@ -88,7 +88,7 @@ public OutGridViewCommand() /// and if it should be possible to select multiple or single list items. /// [Parameter(ParameterSetName = "OutputMode")] - public OutputModeOption OutputMode { set; get; } + public OutputModeOption OutputMode { get; set; } /// /// Gets or sets a value indicating whether the selected items should be written to the pipeline. @@ -97,9 +97,9 @@ public OutGridViewCommand() [Parameter(ParameterSetName = "PassThru")] public SwitchParameter PassThru { - set { this.OutputMode = value.IsPresent ? OutputModeOption.Multiple : OutputModeOption.None; } - get { return OutputMode == OutputModeOption.Multiple ? new SwitchParameter(true) : new SwitchParameter(false); } + + set { this.OutputMode = value.IsPresent ? OutputModeOption.Multiple : OutputModeOption.None; } } #endregion Input Parameters @@ -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) @@ -212,7 +211,7 @@ protected override void StopProcessing() /// PSObject to be converted to a string. internal string ConvertToString(PSObject liveObject) { - StringFormatError formatErrorObject = new StringFormatError(); + StringFormatError formatErrorObject = new(); string smartToString = PSObjectHelper.SmartToString(liveObject, _expressionFactory, InnerFormatShapeCommand.FormatEnumerationLimit(), @@ -265,7 +264,7 @@ baseObject is PSReference || baseObject is FormatInfoData || baseObject is PSObject) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new FormatException(StringUtil.Format(FormatAndOut_out_gridview.DataNotQualifiedForGridView)), DataNotQualifiedForGridView, ErrorCategory.InvalidType, @@ -289,7 +288,7 @@ baseObject is FormatInfoData || Exception exception = _windowProxy.GetLastException(); if (exception != null) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( exception, "ManagementListInvocationException", ErrorCategory.OperationStopped, @@ -324,9 +323,9 @@ internal static GridHeader ConstructGridHeader(PSObject input, OutGridViewComman internal abstract void ProcessInputObject(PSObject input); } - internal class ScalarTypeHeader : GridHeader + internal sealed class ScalarTypeHeader : GridHeader { - private Type _originalScalarType; + private readonly Type _originalScalarType; internal ScalarTypeHeader(OutGridViewCommand parentCmd, PSObject input) : base(parentCmd) { @@ -350,14 +349,14 @@ internal override void ProcessInputObject(PSObject input) } } - internal class NonscalarTypeHeader : GridHeader + internal sealed class NonscalarTypeHeader : GridHeader { - private AppliesTo _appliesTo = null; + private readonly AppliesTo _appliesTo = null; internal NonscalarTypeHeader(OutGridViewCommand parentCmd, PSObject input) : base(parentCmd) { // Prepare a table view. - TableView tableView = new TableView(); + TableView tableView = new(); tableView.Initialize(parentCmd._expressionFactory, parentCmd._typeInfoDataBase); // Request a view definition from the type database. @@ -454,7 +453,7 @@ internal override void ProcessInputObject(PSObject input) } } - internal class HeteroTypeHeader : GridHeader + internal sealed class HeteroTypeHeader : GridHeader { internal HeteroTypeHeader(OutGridViewCommand parentCmd, PSObject input) : base(parentCmd) { @@ -492,13 +491,5 @@ public void Dispose() this.Dispose(true); GC.SuppressFinalize(this); } - - /// - /// Finalizer. - /// - ~OutGridViewCommand() - { - Dispose(false); - } } } 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 55158034368..ef9b0528c75 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands { - internal class OutWindowProxy : IDisposable + internal sealed class OutWindowProxy : IDisposable { private const string OutGridViewWindowClassName = "Microsoft.Management.UI.Internal.OutGridViewWindow"; private const string OriginalTypePropertyName = "OriginalType"; @@ -28,18 +28,18 @@ internal class OutWindowProxy : IDisposable private bool _isWindowStarted; - private string _title; + private readonly string _title; - private OutputModeOption _outputMode; + private readonly OutputModeOption _outputMode; private AutoResetEvent _closedEvent; - private OutGridViewCommand _parentCmdlet; + private readonly OutGridViewCommand _parentCmdlet; - private GraphicalHostReflectionWrapper _graphicalHostReflectionWrapper; + private readonly GraphicalHostReflectionWrapper _graphicalHostReflectionWrapper; /// - /// Initializes a new instance of the OutWindowProxy class. + /// Initializes a new instance of the class. /// internal OutWindowProxy(string title, OutputModeOption outPutMode, OutGridViewCommand parentCmdlet) { @@ -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/ScalarTypeColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs index b72c9b7d439..38cc9668856 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs @@ -6,9 +6,9 @@ namespace Microsoft.PowerShell.Commands { - internal class ScalarTypeColumnInfo : ColumnInfo + internal sealed class ScalarTypeColumnInfo : ColumnInfo { - private Type _type; + private readonly Type _type; internal ScalarTypeColumnInfo(Type type) : base(type.Name, type.Name) @@ -29,7 +29,7 @@ internal override object GetValue(PSObject liveObject) } } - internal class TypeNameColumnInfo : ColumnInfo + internal sealed class TypeNameColumnInfo : ColumnInfo { internal TypeNameColumnInfo(string staleObjectPropertyName, string displayName) : base(staleObjectPropertyName, displayName) @@ -43,9 +43,9 @@ internal override object GetValue(PSObject liveObject) } } - internal class ToStringColumnInfo : ColumnInfo + internal sealed class ToStringColumnInfo : ColumnInfo { - private OutGridViewCommand _parentCmdlet; + private readonly OutGridViewCommand _parentCmdlet; internal ToStringColumnInfo(string staleObjectPropertyName, string displayName, OutGridViewCommand parentCmdlet) : base(staleObjectPropertyName, displayName) @@ -60,7 +60,7 @@ internal override object GetValue(PSObject liveObject) } } - internal class IndexColumnInfo : ColumnInfo + internal sealed class IndexColumnInfo : ColumnInfo { private int _index = 0; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs index dc967889e69..e152bb7c973 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Commands { - internal class TableView + internal sealed class TableView { private PSPropertyExpressionFactory _expressionFactory; private TypeInfoDataBase _typeInfoDatabase; @@ -25,7 +25,7 @@ internal void Initialize(PSPropertyExpressionFactory expressionFactory, _typeInfoDatabase = db; // Initialize Format Error Manager. - FormatErrorPolicy formatErrorPolicy = new FormatErrorPolicy(); + FormatErrorPolicy formatErrorPolicy = new(); formatErrorPolicy.ShowErrorsAsMessages = _typeInfoDatabase.defaultSettingsSection.formatErrorPolicy.ShowErrorsAsMessages; formatErrorPolicy.ShowErrorsInFormattedOutput = _typeInfoDatabase.defaultSettingsSection.formatErrorPolicy.ShowErrorsInFormattedOutput; @@ -35,7 +35,7 @@ internal void Initialize(PSPropertyExpressionFactory expressionFactory, internal HeaderInfo GenerateHeaderInfo(PSObject input, TableControlBody tableBody, OutGridViewCommand parentCmdlet) { - HeaderInfo headerInfo = new HeaderInfo(); + HeaderInfo headerInfo = new(); // This verification is needed because the database returns "LastWriteTime" value for file system objects // as strings and it is used to detect this situation and use the actual field value. @@ -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); @@ -124,7 +119,7 @@ internal HeaderInfo GenerateHeaderInfo(PSObject input, TableControlBody tableBod internal HeaderInfo GenerateHeaderInfo(PSObject input, OutGridViewCommand parentCmdlet) { - HeaderInfo headerInfo = new HeaderInfo(); + HeaderInfo headerInfo = new(); List activeAssociationList; // Get properties from the default property set of the object @@ -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); @@ -191,17 +183,17 @@ internal HeaderInfo GenerateHeaderInfo(PSObject input, OutGridViewCommand parent /// /// None. /// This method updates "activeAssociationList" instance property. - private void FilterActiveAssociationList(List activeAssociationList) + private static void FilterActiveAssociationList(List activeAssociationList) { // we got a valid set of properties from the default property set // make sure we do not have too many properties // NOTE: this is an arbitrary number, chosen to be a sensitive default - int nMax = 256; + const int nMax = 256; if (activeAssociationList.Count > nMax) { - List tmp = new List(activeAssociationList); + List tmp = new(activeAssociationList); activeAssociationList.Clear(); for (int k = 0; k < nMax; k++) activeAssociationList.Add(tmp[k]); @@ -222,7 +214,7 @@ private List GetActiveTableRowDefinition(TableControlBod TableRowDefinition matchingRowDefinition = null; var typeNames = so.InternalTypeNames; - TypeMatch match = new TypeMatch(_expressionFactory, _typeInfoDatabase, typeNames); + TypeMatch match = new(_expressionFactory, _typeInfoDatabase, typeNames); foreach (TableRowDefinition x in tableBody.optionalDefinitionList) { @@ -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; } } @@ -268,7 +254,7 @@ private List GetActiveTableRowDefinition(TableControlBod } // we have an override, we need to compute the merge of the active cells - List activeRowItemDefinitionList = new List(); + List activeRowItemDefinitionList = new(); int col = 0; foreach (TableRowItemDefinition rowItem in matchingRowDefinition.rowItemDefinitionList) { 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 919264ad180..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 @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Management.Automation; @@ -75,14 +74,14 @@ public string LiteralPath private bool _isLiteralPath = false; - private List _typeDefinitions = new List(); + private readonly List _typeDefinitions = new(); private bool _force; /// /// Force writing a file. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -99,7 +98,7 @@ public SwitchParameter Force /// /// Do not overwrite file if exists. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -119,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 a0225c8ea6a..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,10 +24,10 @@ 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 List(); + private readonly List _inputBuffer = new(); /// /// Expect to group s by default. When receiving input that should not be grouped, @@ -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,10 +68,24 @@ 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 { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; /// /// Gets or sets count of bytes to read from the input stream. @@ -139,12 +153,12 @@ protected override void EndProcessing() /// private List ResolvePaths(string[] path, bool literalPath) { - List pathsToProcess = new List(); + List pathsToProcess = new(); ProviderInfo provider = null; foreach (string currentPath in path) { - List newPaths = new List(); + List newPaths = new(); if (literalPath) { @@ -160,7 +174,7 @@ private List ResolvePaths(string[] path, bool literalPath) { if (!WildcardPattern.ContainsWildcardCharacters(currentPath)) { - ErrorRecord errorRecord = new ErrorRecord(e, "FileNotFound", ErrorCategory.ObjectNotFound, path); + ErrorRecord errorRecord = new(e, "FileNotFound", ErrorCategory.ObjectNotFound, path); WriteError(errorRecord); continue; } @@ -171,7 +185,7 @@ private List ResolvePaths(string[] path, bool literalPath) { // Write a non-terminating error message indicating that path specified is not supported. string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexOnlySupportsFileSystemPaths, currentPath); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(errorMessage), "FormatHexOnlySupportsFileSystemPaths", ErrorCategory.InvalidArgument, @@ -279,7 +293,7 @@ private void ProcessString(string originalString) } } - private static readonly Random _idGenerator = new Random(); + private static readonly Random _idGenerator = new(); private static string GetGroupLabel(Type inputType) => string.Format("{0} ({1}) <{2:X8}>", inputType.Name, inputType.FullName, _idGenerator.Next()); @@ -356,7 +370,7 @@ private void ProcessInputObjects(PSObject inputObject) else { string errorMessage = StringUtil.Format(UtilityCommonStrings.FormatHexTypeNotSupported, obj.GetType()); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(errorMessage), "FormatHexTypeNotSupported", ErrorCategory.InvalidArgument, @@ -377,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) { @@ -410,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) @@ -436,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; @@ -472,7 +475,7 @@ private byte[] ConvertToBytes(object inputObject) /// Offset in the file. private void WriteHexadecimal(Span inputBytes, string path, long offset) { - var bytesPerObject = 16; + const int bytesPerObject = 16; for (int index = 0; index < inputBytes.Length; index += bytesPerObject) { var count = inputBytes.Length - index < bytesPerObject @@ -494,7 +497,7 @@ private void WriteHexadecimal(Span inputBytes, string path, long offset) /// private void WriteHexadecimal(Span inputBytes, long offset, string label) { - var bytesPerObject = 16; + const int bytesPerObject = 16; for (int index = 0; index < inputBytes.Length; index += bytesPerObject) { var count = inputBytes.Length - index < bytesPerObject 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 202250f33a1..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,13 +8,15 @@ 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 { /// - /// Constructor to set the inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public FormatListCommand() { @@ -22,4 +24,3 @@ public FormatListCommand() } } } - 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 813bfad23c0..693a799c809 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs @@ -8,13 +8,15 @@ 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 { /// - /// Constructor to se the inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public FormatCustomCommand() { @@ -30,6 +32,7 @@ public FormatCustomCommand() /// will be determined using property sets, etc. /// [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get { return _props; } @@ -39,10 +42,16 @@ public object[] Property private object[] _props; + /// + /// Gets or sets the properties to exclude from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + /// /// /// - [ValidateRangeAttribute(1, int.MaxValue)] + [ValidateRange(1, int.MaxValue)] [Parameter] public int Depth { @@ -57,24 +66,38 @@ public int Depth internal override FormattingCommandLineParameters GetCommandLineParameters() { - FormattingCommandLineParameters parameters = new FormattingCommandLineParameters(); + FormattingCommandLineParameters parameters = new(); + + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((_props is not null && _props.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } if (_props != null) { - ParameterProcessor processor = new ParameterProcessor(new FormatObjectParameterDefinition()); - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); + ParameterProcessor processor = new(new FormatObjectParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); parameters.mshParameterList = processor.ProcessParameters(_props, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_props is null || _props.Length == 0) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatObjectParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } parameters.groupByParameter = this.ProcessGroupByParameter(); @@ -86,7 +109,7 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() parameters.expansion = ProcessExpandParameter(); - ComplexSpecificParameters csp = new ComplexSpecificParameters(); + ComplexSpecificParameters csp = new(); csp.maxDepth = _depth; parameters.shapeParameters = csp; @@ -94,4 +117,3 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() } } } - 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 6939c38643d..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,13 +8,15 @@ 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 { /// - /// Constructor to set the inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public FormatTableCommand() { @@ -22,4 +24,3 @@ public FormatTableCommand() } } } - 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 5927244d896..c6aef5c20be 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; @@ -11,13 +10,15 @@ 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 { /// - /// Constructor to se the inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public FormatWideCommand() { @@ -41,20 +42,19 @@ public object Property private object _prop; /// - /// Optional, non positional parameter. + /// Gets or sets the properties to exclude from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + + /// + /// Gets or sets a value indicating whether to autosize the output. /// - /// [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; @@ -64,17 +64,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; @@ -83,43 +77,54 @@ public int Column internal override FormattingCommandLineParameters GetCommandLineParameters() { - FormattingCommandLineParameters parameters = new FormattingCommandLineParameters(); + FormattingCommandLineParameters parameters = new(); + + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if (_prop is not null || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } if (_prop != null) { - ParameterProcessor processor = new ParameterProcessor(new FormatWideParameterDefinition()); - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); + ParameterProcessor processor = new(new FormatWideParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); parameters.mshParameterList = processor.ProcessParameters(new object[] { _prop }, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_prop is null) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatWideParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } // we cannot specify -column and -autosize, they are mutually exclusive - 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 ErrorRecord( - 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(); @@ -134,7 +139,7 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() if (_autosize.HasValue) parameters.autosize = _autosize.Value; - WideSpecificParameters wideSpecific = new WideSpecificParameters(); + WideSpecificParameters wideSpecific = new(); parameters.shapeParameters = wideSpecific; if (_column.HasValue) { @@ -145,4 +150,3 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() } } } - 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 642139c07d9..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 @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Management.Automation; -using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Text; @@ -27,7 +25,8 @@ internal static FileMode Convert(OpenMode openMode) public class OutFileCommand : FrontEndCommandBase { /// - /// Set inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public OutFileCommand() { @@ -75,15 +74,29 @@ public string LiteralPath /// Encoding optional flag. /// [Parameter(Position = 1)] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; /// /// Property that sets append parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Append { get { return _append; } @@ -96,7 +109,7 @@ public SwitchParameter Append /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return _force; } @@ -109,7 +122,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -123,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 { @@ -160,7 +173,7 @@ public SwitchParameter NoNewline /// protected override void BeginProcessing() { - // set up the Scree Host interface + // set up the Screen Host interface OutputManagerInner outInner = (OutputManagerInner)this.implementation; // NOTICE: if any exception is thrown from here to the end of the method, the @@ -215,7 +228,7 @@ private LineOutput InstantiateLineOutputInterface() } // use the stream writer to create and initialize the Line Output writer - TextWriterLineOutput twlo = new TextWriterLineOutput(_sw, computedWidth, _suppressNewline); + TextWriterLineOutput twlo = new(_sw, computedWidth, _suppressNewline); // finally have the ILineOutput interface extracted return (LineOutput)twlo; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/Out-Printer.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/Out-Printer.cs index 67c173b2077..58342172906 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/Out-Printer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-printer/Out-Printer.cs @@ -14,7 +14,8 @@ namespace Microsoft.PowerShell.Commands public class OutPrinterCommand : FrontEndCommandBase { /// - /// Set inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public OutPrinterCommand() { @@ -41,7 +42,7 @@ public string Name /// protected override void BeginProcessing() { - // set up the Scree Host interface + // set up the Screen Host interface OutputManagerInner outInner = (OutputManagerInner)this.implementation; outInner.LineOutput = InstantiateLineOutputInterface(); @@ -55,7 +56,7 @@ protected override void BeginProcessing() /// private LineOutput InstantiateLineOutputInterface() { - PrinterLineOutput printOutput = new PrinterLineOutput(_printerName); + PrinterLineOutput printOutput = new(_printerName); return (LineOutput)printOutput; } } 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 5616a7fa436..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,14 +64,29 @@ 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 /// + /// Initializes static members of the class. /// Used for static initializations like DefaultPrintFontName. /// static PrinterLineOutput() @@ -81,7 +98,7 @@ static PrinterLineOutput() } /// - /// Constructor for the class. + /// Initializes a new instance of the class. /// /// Name of printer, if null use default printer. internal PrinterLineOutput(string printerName) @@ -89,8 +106,8 @@ internal PrinterLineOutput(string printerName) _printerName = printerName; // instantiate the helper to do the line processing when LineOutput.WriteXXX() is called - WriteLineHelper.WriteCallback wl = new WriteLineHelper.WriteCallback(this.OnWriteLine); - WriteLineHelper.WriteCallback w = new WriteLineHelper.WriteCallback(this.OnWrite); + WriteLineHelper.WriteCallback wl = new(this.OnWriteLine); + WriteLineHelper.WriteCallback w = new(this.OnWrite); _writeLineHelper = new WriteLineHelper(true, wl, w, this.DisplayCells); } @@ -123,7 +140,7 @@ private void DoPrint() try { // create a new print document object and set the printer name, if available - PrintDocument pd = new PrintDocument(); + PrintDocument pd = new(); if (!string.IsNullOrEmpty(_printerName)) { @@ -131,7 +148,7 @@ private void DoPrint() } // set up the callback mechanism - pd.PrintPage += new PrintPageEventHandler(this.pd_PrintPage); + pd.PrintPage += this.pd_PrintPage; // start printing pd.Print(); @@ -187,9 +204,9 @@ private void VerifyFont(Graphics g) // we compute the length of two strings, one made of "large" characters // one made of "narrow" ones. If they are the same length, we assume that // the font is fixed pitch. - string large = "ABCDEF"; + const string large = "ABCDEF"; float wLarge = g.MeasureString(large, _printFont).Width / large.Length; - string narrow = ".;'}l|"; + const string narrow = ".;'}l|"; float wNarrow = g.MeasureString(narrow, _printFont).Width / narrow.Length; if (Math.Abs((float)(wLarge - wNarrow)) < 0.001F) @@ -229,7 +246,7 @@ private void pd_PrintPage(object sender, PrintPageEventArgs ev) // on the first page we have to initialize the metrics for LineOutput // work out the number of columns per page assuming fixed pitch font - string s = "ABCDEF"; + const string s = "ABCDEF"; float w = ev.Graphics.MeasureString(s, _printFont).Width / s.Length; float columnsPerPage = ev.MarginBounds.Width / w; @@ -276,7 +293,7 @@ private void pd_PrintPage(object sender, PrintPageEventArgs ev) /// /// Name of the printer to print to. Null means default printer. /// - private string _printerName = null; + private readonly string _printerName = null; /// /// Name of the font to use, if null the default is used. @@ -315,13 +332,13 @@ private void pd_PrintPage(object sender, PrintPageEventArgs ev) /// /// Text lines ready to print (after output cache playback). /// - private Queue _lines = new Queue(); + private readonly Queue _lines = new(); /// /// Cached font object. /// private Font _printFont = null; - private WriteLineHelper _writeLineHelper; + private readonly WriteLineHelper _writeLineHelper; } } 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 5dcb3c4f588..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 @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; -using System.Management.Automation.Host; using System.Text; using Microsoft.PowerShell.Commands.Internal.Format; @@ -36,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 { @@ -63,7 +61,8 @@ public SwitchParameter NoNewline #endregion /// - /// Set inner command. + /// Initializes a new instance of the class + /// and sets the inner command. /// public OutStringCommand() { @@ -91,7 +90,7 @@ protected override void BeginProcessing() private LineOutput InstantiateLineOutputInterface() { // set up the streaming text writer - StreamingTextWriter.WriteLineCallback callback = new StreamingTextWriter.WriteLineCallback(this.OnWriteLine); + StreamingTextWriter.WriteLineCallback callback = new(this.OnWriteLine); _writer = new StreamingTextWriter(callback, Host.CurrentCulture); @@ -105,7 +104,7 @@ private LineOutput InstantiateLineOutputInterface() } // use it to create and initialize the Line Output writer - TextWriterLineOutput twlo = new TextWriterLineOutput(_writer, computedWidth); + TextWriterLineOutput twlo = new(_writer, computedWidth); // finally have the LineOutput interface extracted return (LineOutput)twlo; @@ -166,6 +165,6 @@ protected override void EndProcessing() /// /// Buffer used when buffering until the end. /// - private StringBuilder _buffer = new StringBuilder(); + private readonly StringBuilder _buffer = new(); } } 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 7c2fdbe0c14..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 @@ -72,7 +72,7 @@ protected override void ProcessRecord() } index = 0; - bool addErrorIdentifier = errorRecords.Count > 1 ? true : false; + bool addErrorIdentifier = errorRecords.Count > 1; foreach (object errorRecord in errorRecords) { 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 b836fafc9f9..c675a0f6dc1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs @@ -16,168 +16,106 @@ public enum BreakpointType /// Breakpoint on a line within a script Line, - /// - /// Breakpoint on a variable + /// Breakpoint on a variable Variable, /// Breakpoint on a command Command - }; + } /// /// This class implements Get-PSBreakpoint. /// - [Cmdlet(VerbsCommon.Get, "PSBreakpoint", DefaultParameterSetName = "Script", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097108")] - [OutputType(typeof(Breakpoint))] - public class GetPSBreakpointCommand : PSCmdlet + [Cmdlet(VerbsCommon.Get, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097108")] + [OutputType(typeof(CommandBreakpoint), ParameterSetName = new[] { CommandParameterSetName })] + [OutputType(typeof(LineBreakpoint), ParameterSetName = new[] { LineParameterSetName })] + [OutputType(typeof(VariableBreakpoint), ParameterSetName = new[] { VariableParameterSetName })] + [OutputType(typeof(Breakpoint), ParameterSetName = new[] { TypeParameterSetName, IdParameterSetName })] + public class GetPSBreakpointCommand : PSBreakpointAccessorCommandBase { + #region strings + + internal const string TypeParameterSetName = "Type"; + internal const string IdParameterSetName = "Id"; + + #endregion strings + #region parameters /// /// Scripts of the breakpoints to output. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")] - [Parameter(ParameterSetName = "Script", Position = 0, ValueFromPipeline = true)] - [Parameter(ParameterSetName = "Variable")] - [Parameter(ParameterSetName = "Command")] - [Parameter(ParameterSetName = "Type")] + [Parameter(ParameterSetName = LineParameterSetName, Position = 0, ValueFromPipeline = true)] + [Parameter(ParameterSetName = CommandParameterSetName)] + [Parameter(ParameterSetName = VariableParameterSetName)] + [Parameter(ParameterSetName = TypeParameterSetName)] [ValidateNotNullOrEmpty()] - public string[] Script - { - get - { - return _script; - } - - set - { - _script = value; - } - } - - private string[] _script; + public string[] Script { get; set; } /// /// IDs of the breakpoints to output. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")] - [Parameter(ParameterSetName = "Id", Mandatory = true, Position = 0, ValueFromPipeline = true)] + [Parameter(ParameterSetName = IdParameterSetName, Mandatory = true, Position = 0, ValueFromPipeline = true)] [ValidateNotNull] - public int[] Id - { - get - { - return _id; - } - - set - { - _id = value; - } - } - - private int[] _id; + public int[] Id { get; set; } /// /// Variables of the breakpoints to output. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")] - [Parameter(ParameterSetName = "Variable", Mandatory = true)] + [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)] [ValidateNotNull] - public string[] Variable - { - get - { - return _variable; - } - - set - { - _variable = value; - } - } - - private string[] _variable; + public string[] Variable { get; set; } /// /// Commands of the breakpoints to output. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")] - [Parameter(ParameterSetName = "Command", Mandatory = true)] + [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)] [ValidateNotNull] - public string[] Command - { - get - { - return _command; - } - - set - { - _command = value; - } - } - - private string[] _command; + public string[] Command { get; set; } /// /// Commands of the breakpoints to output. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "It's OK to use arrays for cmdlet parameters")] [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Type is OK for a cmdlet parameter")] - [Parameter(ParameterSetName = "Type", Mandatory = true, Position = 0, ValueFromPipeline = true)] + [Parameter(ParameterSetName = TypeParameterSetName, Mandatory = true, Position = 0, ValueFromPipeline = true)] [ValidateNotNull] - public BreakpointType[] Type - { - get - { - return _type; - } - - set - { - _type = value; - } - } - - private BreakpointType[] _type; + public BreakpointType[] Type { get; set; } #endregion parameters + #region overrides + /// /// Remove breakpoints. /// protected override void ProcessRecord() { - List breakpoints = Context.Debugger.GetBreakpoints(); + List breakpoints = Runspace.Debugger.GetBreakpoints(); - // // Filter by parameter set - // - if (this.ParameterSetName.Equals("Script", StringComparison.OrdinalIgnoreCase)) + if (ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)) { // no filter } - else if (this.ParameterSetName.Equals("Id", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(IdParameterSetName, StringComparison.OrdinalIgnoreCase)) { breakpoints = Filter( breakpoints, - _id, - delegate (Breakpoint breakpoint, int id) - { - return breakpoint.Id == id; - } - ); + Id, + static (Breakpoint breakpoint, int id) => breakpoint.Id == id); } - else if (this.ParameterSetName.Equals("Command", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { breakpoints = Filter( breakpoints, - _command, - delegate (Breakpoint breakpoint, string command) + Command, + (Breakpoint breakpoint, string command) => { - CommandBreakpoint commandBreakpoint = breakpoint as CommandBreakpoint; - - if (commandBreakpoint == null) + if (breakpoint is not CommandBreakpoint commandBreakpoint) { return false; } @@ -185,16 +123,14 @@ protected override void ProcessRecord() return commandBreakpoint.Command.Equals(command, StringComparison.OrdinalIgnoreCase); }); } - else if (this.ParameterSetName.Equals("Variable", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) { breakpoints = Filter( breakpoints, - _variable, - delegate (Breakpoint breakpoint, string variable) + Variable, + (Breakpoint breakpoint, string variable) => { - VariableBreakpoint variableBreakpoint = breakpoint as VariableBreakpoint; - - if (variableBreakpoint == null) + if (breakpoint is not VariableBreakpoint variableBreakpoint) { return false; } @@ -202,12 +138,12 @@ protected override void ProcessRecord() return variableBreakpoint.Variable.Equals(variable, StringComparison.OrdinalIgnoreCase); }); } - else if (this.ParameterSetName.Equals("Type", StringComparison.OrdinalIgnoreCase)) + else if (ParameterSetName.Equals(TypeParameterSetName, StringComparison.OrdinalIgnoreCase)) { breakpoints = Filter( breakpoints, - _type, - delegate (Breakpoint breakpoint, BreakpointType type) + Type, + (Breakpoint breakpoint, BreakpointType type) => { switch (type) { @@ -244,15 +180,13 @@ protected override void ProcessRecord() Diagnostics.Assert(false, "Invalid parameter set: {0}", this.ParameterSetName); } - // // Filter by script - // - if (_script != null) + if (Script != null) { breakpoints = Filter( breakpoints, - _script, - delegate (Breakpoint breakpoint, string script) + Script, + (Breakpoint breakpoint, string script) => { if (breakpoint.Script == null) { @@ -267,15 +201,17 @@ protected override void ProcessRecord() }); } - // // Output results - // foreach (Breakpoint b in breakpoints) { - WriteObject(b); + ProcessBreakpoint(b); } } + #endregion overrides + + #region private methods + /// /// Gives the criteria to filter breakpoints. /// @@ -285,9 +221,9 @@ protected override void ProcessRecord() /// Returns the items in the input list that match an item in the filter array according to /// the given selection criterion. /// - private List Filter(List input, T[] filter, FilterSelector selector) + private static List Filter(List input, T[] filter, FilterSelector selector) { - List output = new List(); + List output = new(); for (int i = 0; i < input.Count; i++) { @@ -303,5 +239,7 @@ private List Filter(List input, T[] filter, FilterSel return output; } + + #endregion private methods } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetAliasCommand.cs index 49baa9bd296..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; } /// @@ -118,7 +119,7 @@ private void WriteMatches(string value, string parametersetname) _excludes, WildcardOptions.IgnoreCase); - List results = new List(); + List results = new(); foreach (KeyValuePair tableEntry in aliasTable) { if (parametersetname.Equals("Definition", StringComparison.OrdinalIgnoreCase)) @@ -181,10 +182,7 @@ private void WriteMatches(string value, string parametersetname) } results.Sort( - delegate (AliasInfo left, AliasInfo right) - { - return 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); @@ -195,8 +193,8 @@ private void WriteMatches(string value, string parametersetname) // Need to write an error if the user tries to get an alias // tat doesn't exist and they are not globbing. - ItemNotFoundException itemNotFound = new ItemNotFoundException(StringUtil.Format(AliasCommandStrings.NoAliasFound, displayString, value)); - ErrorRecord er = new ErrorRecord(itemNotFound, "ItemNotFoundException", ErrorCategory.ObjectNotFound, value); + ItemNotFoundException itemNotFound = new(StringUtil.Format(AliasCommandStrings.NoAliasFound, displayString, value)); + ErrorRecord er = new(itemNotFound, "ItemNotFoundException", ErrorCategory.ObjectNotFound, value); WriteError(er); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs index 4516568cc34..6717cc7196b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs @@ -14,9 +14,9 @@ namespace Microsoft.PowerShell.Commands /// /// Implementation for the get-date command. /// - [Cmdlet(VerbsCommon.Get, "Date", DefaultParameterSetName = "net", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096615")] - [OutputType(typeof(string), ParameterSetName = new string[] { "UFormat", "net" })] - [OutputType(typeof(DateTime), ParameterSetName = new string[] { "net" })] + [Cmdlet(VerbsCommon.Get, "Date", DefaultParameterSetName = DateAndFormatParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096615")] + [OutputType(typeof(string))] + [OutputType(typeof(DateTime), ParameterSetName = new[] { DateAndFormatParameterSet, UnixTimeSecondsAndFormatParameterSet })] public sealed class GetDateCommand : Cmdlet { #region parameters @@ -24,7 +24,8 @@ public sealed class GetDateCommand : Cmdlet /// /// Allows user to override the date/time object that will be processed. /// - [Parameter(Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = DateAndFormatParameterSet, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] + [Parameter(ParameterSetName = DateAndUFormatParameterSet, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] [Alias("LastWriteTime")] public DateTime Date { @@ -40,20 +41,44 @@ public DateTime Date } } + private DateTime _date; + private bool _dateSpecified; + + // The const comes from DateTimeOffset.MinValue.ToUnixTimeSeconds() + private const long MinimumUnixTimeSecond = -62135596800; + + // The const comes from DateTimeOffset.MaxValue.ToUnixTimeSeconds() + private const long MaximumUnixTimeSecond = 253402300799; + /// /// Gets or sets whether to treat a numeric input as ticks, or unix time. /// - [Parameter] - public SwitchParameter FromUnixTime; + [Parameter(ParameterSetName = UnixTimeSecondsAndFormatParameterSet, Mandatory = true)] + [Parameter(ParameterSetName = UnixTimeSecondsAndUFormatParameterSet, Mandatory = true)] + [ValidateRange(MinimumUnixTimeSecond, MaximumUnixTimeSecond)] + [Alias("UnixTime")] + public long UnixTimeSeconds + { + get + { + return _unixTimeSeconds; + } - private DateTime _date; - private bool _dateSpecified; + set + { + _unixTimeSeconds = value; + _unixTimeSecondsSpecified = true; + } + } + + private long _unixTimeSeconds; + private bool _unixTimeSecondsSpecified; /// /// Allows the user to override the year. /// [Parameter] - [ValidateRangeAttribute(1, 9999)] + [ValidateRange(1, 9999)] public int Year { get @@ -75,7 +100,7 @@ public int Year /// Allows the user to override the month. /// [Parameter] - [ValidateRangeAttribute(1, 12)] + [ValidateRange(1, 12)] public int Month { get @@ -97,7 +122,7 @@ public int Month /// Allows the user to override the day. /// [Parameter] - [ValidateRangeAttribute(1, 31)] + [ValidateRange(1, 31)] public int Day { get @@ -119,7 +144,7 @@ public int Day /// Allows the user to override the hour. /// [Parameter] - [ValidateRangeAttribute(0, 23)] + [ValidateRange(0, 23)] public int Hour { get @@ -141,7 +166,7 @@ public int Hour /// Allows the user to override the minute. /// [Parameter] - [ValidateRangeAttribute(0, 59)] + [ValidateRange(0, 59)] public int Minute { get @@ -163,7 +188,7 @@ public int Minute /// Allows the user to override the second. /// [Parameter] - [ValidateRangeAttribute(0, 59)] + [ValidateRange(0, 59)] public int Second { get @@ -185,7 +210,7 @@ public int Second /// Allows the user to override the millisecond. /// [Parameter] - [ValidateRangeAttribute(0, 999)] + [ValidateRange(0, 999)] public int Millisecond { get @@ -212,21 +237,22 @@ public int Millisecond /// /// Unix format string. /// - [Parameter(ParameterSetName = "UFormat")] - [ValidateNotNullOrEmpty] + [Parameter(ParameterSetName = DateAndUFormatParameterSet, Mandatory = true)] + [Parameter(ParameterSetName = UnixTimeSecondsAndUFormatParameterSet, Mandatory = true)] public string UFormat { get; set; } /// - /// Unix format string. + /// DotNet format string. /// - [Parameter(ParameterSetName = "net")] + [Parameter(ParameterSetName = DateAndFormatParameterSet)] + [Parameter(ParameterSetName = UnixTimeSecondsAndFormatParameterSet)] [ArgumentCompletions("FileDate", "FileDateUniversal", "FileDateTime", "FileDateTimeUniversal")] public string Format { get; set; } /// /// Gets or sets a value that converts date to UTC before formatting. /// - [Parameter(ParameterSetName = "net")] + [Parameter] public SwitchParameter AsUTC { get; set; } #endregion @@ -243,14 +269,11 @@ protected override void ProcessRecord() // use passed date object if specified if (_dateSpecified) { - if (FromUnixTime.IsPresent) - { - dateToUse = DateTimeOffset.FromUnixTimeSeconds(Date.Ticks).UtcDateTime; - } - else - { - dateToUse = Date; - } + dateToUse = Date; + } + else if (_unixTimeSecondsSpecified) + { + dateToUse = DateTimeOffset.FromUnixTimeSeconds(UnixTimeSeconds).LocalDateTime; } // use passed year if specified @@ -345,23 +368,21 @@ protected override void ProcessRecord() else { // output DateTime object wrapped in an PSObject with DisplayHint attached - PSObject outputObj = new PSObject(dateToUse); - PSNoteProperty note = new PSNoteProperty("DisplayHint", DisplayHint); + PSObject outputObj = new(dateToUse); + PSNoteProperty note = new("DisplayHint", DisplayHint); outputObj.Properties.Add(note); WriteObject(outputObj); } } - private static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - /// /// This is more an implementation of the UNIX strftime. /// private string UFormatDateString(DateTime dateTime) { int offset = 0; - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); // folks may include the "+" as part of the format string if (UFormat[0] == '+') @@ -417,11 +438,12 @@ private string UFormatDateString(DateTime dateTime) break; case 'G': - sb.Append("{0:yyyy}"); + sb.Append(StringUtil.Format("{0:0000}", ISOWeek.GetYear(dateTime))); break; case 'g': - sb.Append("{0:yy}"); + int isoYearWithoutCentury = ISOWeek.GetYear(dateTime) % 100; + sb.Append(StringUtil.Format("{0:00}", isoYearWithoutCentury)); break; case 'H': @@ -457,7 +479,7 @@ private string UFormatDateString(DateTime dateTime) break; case 'n': - sb.Append("\n"); + sb.Append('\n'); break; case 'p': @@ -477,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': @@ -485,7 +507,7 @@ private string UFormatDateString(DateTime dateTime) break; case 't': - sb.Append("\t"); + sb.Append('\t'); break; case 'U': @@ -493,7 +515,8 @@ private string UFormatDateString(DateTime dateTime) break; case 'u': - sb.Append((int)dateTime.DayOfWeek); + int dayOfWeek = dateTime.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)dateTime.DayOfWeek; + sb.Append(dayOfWeek); break; case 'V': @@ -544,6 +567,11 @@ private string UFormatDateString(DateTime dateTime) } #endregion + + private const string DateAndFormatParameterSet = "DateAndFormat"; + private const string DateAndUFormatParameterSet = "DateAndUFormat"; + private const string UnixTimeSecondsAndFormatParameterSet = "UnixTimeSecondsAndFormat"; + private const string UnixTimeSecondsAndUFormatParameterSet = "UnixTimeSecondsAndUFormat"; } #endregion diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventCommand.cs index faf16d384f7..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 @@ -124,7 +124,7 @@ protected override void EndProcessing() error = EventingStrings.EventIdentifierNotFound; } - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, error, identifier)), "INVALID_SOURCE_IDENTIFIER", ErrorCategory.InvalidArgument, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetEventSubscriberCommand.cs index cde73b2914d..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 @@ -67,7 +67,7 @@ protected override void ProcessRecord() // Go through all the received events and write them to the output // pipeline - List subscribers = new List(Events.Subscribers); + List subscribers = new(Events.Subscribers); foreach (PSEventSubscriber subscriber in subscribers) { // If they specified a event identifier and we don't match, continue @@ -118,7 +118,7 @@ protected override void ProcessRecord() error = EventingStrings.EventSubscriptionNotFound; } - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(string.Format(System.Globalization.CultureInfo.CurrentCulture, error, identifier)), "INVALID_SOURCE_IDENTIFIER", ErrorCategory.InvalidArgument, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHash.cs index 65e55a86ab1..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,22 +69,13 @@ 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. /// protected override void ProcessRecord() { - List pathsToProcess = new List(); + List pathsToProcess = new(); ProviderInfo provider = null; switch (ParameterSetName) @@ -104,7 +96,7 @@ protected override void ProcessRecord() { if (!WildcardPattern.ContainsWildcardCharacters(path)) { - ErrorRecord errorRecord = new ErrorRecord(e, + ErrorRecord errorRecord = new(e, "FileNotFound", ErrorCategory.ObjectNotFound, path); @@ -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) { @@ -211,7 +219,7 @@ private bool ComputeFileHash(string path, out string hash) /// private void WriteHashResult(string Algorithm, string hash, string path) { - FileHashInfo result = new FileHashInfo(); + FileHashInfo result = new(); result.Algorithm = Algorithm; result.Hash = hash; result.Path = path; @@ -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/GetHostCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHostCmdlet.cs index 76718bfde3f..fec8bafe34a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHostCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetHostCmdlet.cs @@ -3,14 +3,11 @@ using System.Management.Automation; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell.Commands { /// /// Writes the PSHost object to the success stream. /// - [Cmdlet(VerbsCommon.Get, "Host", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097110", RemotingCapability = RemotingCapability.None)] [OutputType(typeof(System.Management.Automation.Host.PSHost))] public diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs index daa870d9b1a..0b11af84200 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetMember.cs @@ -6,7 +6,6 @@ using System.Collections.Specialized; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Reflection; namespace Microsoft.PowerShell.Commands { @@ -23,7 +22,7 @@ public override string ToString() return Definition; } /// - /// Initializes a new instance of this class. + /// Initializes a new instance of the class. /// public MemberDefinition(string typeName, string name, PSMemberTypes memberType, string definition) { @@ -65,21 +64,21 @@ public class GetMemberCommand : PSCmdlet /// The object to retrieve properties from. /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } + public PSObject InputObject { get; set; } /// /// The member names to be retrieved. /// [Parameter(Position = 0)] [ValidateNotNullOrEmpty] - public string[] Name { set; get; } = new string[] { "*" }; + public string[] Name { get; set; } = new string[] { "*" }; /// /// The member types to be retrieved. /// [Parameter] [Alias("Type")] - public PSMemberTypes MemberType { set; get; } = PSMemberTypes.All; + public PSMemberTypes MemberType { get; set; } = PSMemberTypes.All; /// /// View from which the members are retrieved. @@ -94,9 +93,9 @@ public class GetMemberCommand : PSCmdlet [Parameter] public SwitchParameter Static { - set { _staticParameter = value; } - get { return _staticParameter; } + + set { _staticParameter = value; } } /// @@ -132,7 +131,7 @@ public SwitchParameter Force private MshMemberMatchOptions _matchOptions = MshMemberMatchOptions.None; - private HybridDictionary _typesAlreadyDisplayed = new HybridDictionary(); + private readonly HybridDictionary _typesAlreadyDisplayed = new(); /// /// This method implements the ProcessRecord method for get-member command. @@ -231,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; } @@ -250,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) { @@ -272,7 +270,7 @@ protected override void EndProcessing() { if (_typesAlreadyDisplayed.Count == 0) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(GetMember.NoObjectSpecified), "NoObjectInGetMember", ErrorCategory.CloseError, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommand.cs index 01cf6534c48..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 ErrorRecord( - new ArgumentException(string.Format( - CultureInfo.InvariantCulture, GetRandomCommandStrings.MinGreaterThanOrEqualMax, minValue, maxValue)), - "MinGreaterThanOrEqualMax", - ErrorCategory.InvalidArgument, - null); - - ThrowTerminatingError(errorRecord); - } - - #endregion - - #region Random generator state - - private static ReaderWriterLockSlim s_runspaceGeneratorMapLock = new ReaderWriterLockSlim(); - - // 1-to-1 mapping of runspaces and random number generators - private static Dictionary s_runspaceGeneratorMap = new Dictionary(); - - 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 bool IsInt(object o) - { - if (o == null || o is int) - { - return true; - } - - return false; - } - - private bool IsInt64(object o) - { - if (o == null || o is Int64) - { - return true; - } - - return false; - } - - private 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 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 = 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 - { - /// - /// Constructor. - /// - public PolymorphicRandomNumberGenerator() - { - _cryptographicGenerator = RandomNumberGenerator.Create(); - _pseudoGenerator = null; - } - - internal PolymorphicRandomNumberGenerator(int seed) - { - _cryptographicGenerator = null; - _pseudoGenerator = new Random(seed); - } - - private Random _pseudoGenerator = null; - private 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..8e50b1cf5a9 --- /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 (Shuffle) + { + // 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 sealed 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/GetRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRunspaceCommand.cs index 0aedd4c9d06..10c050a4562 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRunspaceCommand.cs @@ -126,7 +126,7 @@ internal static IReadOnlyList GetAllRunspaces() internal static IReadOnlyList GetRunspacesByName(string[] names) { - List rtnRunspaces = new List(); + List rtnRunspaces = new(); IReadOnlyList runspaces = Runspace.RunspaceList; foreach (string name in names) @@ -146,7 +146,7 @@ internal static IReadOnlyList GetRunspacesByName(string[] names) internal static IReadOnlyList GetRunspacesById(int[] ids) { - List rtnRunspaces = new List(); + List rtnRunspaces = new(); foreach (int id in ids) { @@ -167,7 +167,7 @@ internal static IReadOnlyList GetRunspacesById(int[] ids) internal static IReadOnlyList GetRunspacesByInstanceId(Guid[] instanceIds) { - List rtnRunspaces = new List(); + List rtnRunspaces = new(); IReadOnlyList runspaces = Runspace.RunspaceList; foreach (Guid instanceId in instanceIds) 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 ea33f7c28d7..09bc78d9693 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUnique.cs @@ -19,7 +19,7 @@ public sealed class GetUniqueCommand : PSCmdlet /// /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// This parameter specifies that objects should be converted to @@ -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,15 +95,12 @@ 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 = (0 != _comparer.Compare(InputObject, _lastObject)); + isUnique = (_comparer.Compare(InputObject, _lastObject) != 0); } if (isUnique) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs index e8dcfbe254a..c21165301e2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs @@ -36,16 +36,15 @@ protected override void ProcessRecord() { TimeSpan uptime = TimeSpan.FromSeconds(Stopwatch.GetTimestamp() / Stopwatch.Frequency); - switch (ParameterSetName) + if (Since) { - case TimespanParameterSet: - // return TimeSpan of time since the system started up - WriteObject(uptime); - break; - case SinceParameterSet: - // return Datetime when the system started up - WriteObject(DateTime.Now.Subtract(uptime)); - break; + // Output the time of the last system boot. + WriteObject(DateTime.Now.Subtract(uptime)); + } + else + { + // Output the time elapsed since the last system boot. + WriteObject(uptime); } } else diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs index 766b38116c0..61a0fe1a390 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetVerbCommand.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Management.Automation; -using System.Reflection; +using static System.Management.Automation.Verbs; namespace Microsoft.PowerShell.Commands { @@ -20,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; @@ -40,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 VerbInfo(); - 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 9938dc15c7f..1f527258939 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Group-Object.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -141,7 +140,7 @@ internal virtual void Add(PSObject groupValue) private static string BuildName(List propValues) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (ObjectCommandPropertyValue propValue in propValues) { var propValuePropertyValue = propValue?.PropertyValue; @@ -149,12 +148,12 @@ private static string BuildName(List propValues) { if (propValuePropertyValue is ICollection propertyValueItems) { - sb.Append("{"); + sb.Append('{'); var length = sb.Length; 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; @@ -162,7 +161,7 @@ private static string BuildName(List propValues) } else { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}, ", propValuePropertyValue.ToString()); + sb.Append(CultureInfo.CurrentCulture, $"{propValuePropertyValue}, "); } } } @@ -177,7 +176,7 @@ public ArrayList Values { get { - ArrayList values = new ArrayList(); + ArrayList values = new(); foreach (ObjectCommandPropertyValue propValue in GroupValue.orderValues) { values.Add(propValue.PropertyValue); @@ -249,10 +248,10 @@ public class GroupObjectCommand : ObjectBase [Parameter(ParameterSetName = "HashTable")] public SwitchParameter AsString { get; set; } - private readonly List _groups = new List(); - private readonly OrderByProperty _orderByProperty = new OrderByProperty(); - private readonly Dictionary _tupleToGroupInfoMappingDictionary = new Dictionary(); - private readonly List _entriesToOrder = new List(); + private readonly List _groups = new(); + private readonly OrderByProperty _orderByProperty = new(); + private readonly Dictionary _tupleToGroupInfoMappingDictionary = new(); + private readonly List _entriesToOrder = new(); private OrderByPropertyComparer _orderByPropertyComparer; private bool _hasProcessedFirstInputObject; private bool _hasDifferentValueTypes; @@ -376,7 +375,7 @@ private static void DoOrderedGrouping( private void WriteNonTerminatingError(Exception exception, string resourceIdAndErrorId, ErrorCategory category) { - Exception ex = new Exception(StringUtil.Format(resourceIdAndErrorId), exception); + Exception ex = new(StringUtil.Format(resourceIdAndErrorId), exception); WriteError(new ErrorRecord(ex, resourceIdAndErrorId, category, null)); } @@ -393,24 +392,21 @@ protected override void ProcessRecord() if (!_hasProcessedFirstInputObject) { - if (Property == null) - { - Property = OrderByProperty.GetDefaultKeyPropertySet(InputObject); - } + Property ??= OrderByProperty.GetDefaultKeyPropertySet(InputObject); _orderByProperty.ProcessExpressionParameter(this, Property); if (AsString && !AsHashTable) { - ArgumentException ex = new ArgumentException(UtilityCommonStrings.GroupObjectWithHashTable); - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, AsString); + ArgumentException ex = new(UtilityCommonStrings.GroupObjectWithHashTable); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.InvalidArgument, AsString); ThrowTerminatingError(er); } if (AsHashTable && !AsString && (Property != null && (Property.Length > 1 || _orderByProperty.MshParameterList.Count > 1))) { - ArgumentException ex = new ArgumentException(UtilityCommonStrings.GroupObjectSingleProperty); - ErrorRecord er = new ErrorRecord(ex, "ArgumentException", ErrorCategory.InvalidArgument, Property); + ArgumentException ex = new(UtilityCommonStrings.GroupObjectSingleProperty); + ErrorRecord er = new(ex, "ArgumentException", ErrorCategory.InvalidArgument, Property); ThrowTerminatingError(er); } @@ -444,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; } @@ -492,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 58d9d762ab7..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -73,10 +73,24 @@ public SwitchParameter Force /// Encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; #endregion Parameters @@ -113,7 +127,7 @@ protected override void BeginProcessing() if (IsModuleSpecified && IsFullyQualifiedModuleSpecified) { string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, nameof(Module), nameof(FullyQualifiedModule)); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); + ErrorRecord error = new(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(error); } @@ -243,9 +257,9 @@ private PSModuleInfo CreateModule(string manifestFile) [ValidateNotNullOrEmpty] public new string Prefix { - set { base.Prefix = value; } - get { return base.Prefix; } + + set { base.Prefix = value; } } /// @@ -275,7 +289,7 @@ protected override void BeginProcessing() if (IsModuleSpecified && IsFullyQualifiedModuleSpecified) { string errMsg = StringUtil.Format(SessionStateStrings.GetContent_TailAndHeadCannotCoexist, nameof(Module), nameof(FullyQualifiedModule)); - ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); + ErrorRecord error = new(new InvalidOperationException(errMsg), "ModuleAndFullyQualifiedModuleCannotBeSpecifiedTogether", ErrorCategory.InvalidOperation, null); ThrowTerminatingError(error); } @@ -429,10 +443,7 @@ public string[] Module set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _PSSnapins = value; _commandParameterSpecified = true; @@ -466,7 +477,7 @@ public ModuleSpecification[] FullyQualifiedModule } } - private ModuleSpecification[] _moduleSpecifications = new ModuleSpecification[0]; + private ModuleSpecification[] _moduleSpecifications = Array.Empty(); internal bool IsFullyQualifiedModuleSpecified = false; private bool _commandParameterSpecified; // initialized to default value in the constructor @@ -507,7 +518,7 @@ public string[] FormatTypeName /// /// This parameter specified a prefix used to modify names of imported commands. /// - internal string Prefix { set; get; } = string.Empty; + internal string Prefix { get; set; } = string.Empty; /// /// Gets or sets the certificate with which to sign the format file and psm1 file. @@ -545,11 +556,11 @@ internal ErrorDetails GetErrorDetails(string errorId, params object[] args) private ErrorRecord GetErrorNoCommandsImportedBecauseOfSkipping() { - string errorId = "ErrorNoCommandsImportedBecauseOfSkipping"; + const string errorId = "ErrorNoCommandsImportedBecauseOfSkipping"; ErrorDetails details = this.GetErrorDetails(errorId); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(details.Message), errorId, ErrorCategory.InvalidResult, @@ -566,11 +577,11 @@ private ErrorRecord GetErrorMalformedDataFromRemoteCommand(string commandName) throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } - string errorId = "ErrorMalformedDataFromRemoteCommand"; + const string errorId = "ErrorMalformedDataFromRemoteCommand"; ErrorDetails details = this.GetErrorDetails(errorId, commandName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(details.Message), errorId, ErrorCategory.InvalidResult, @@ -587,11 +598,11 @@ private ErrorRecord GetErrorCommandSkippedBecauseOfShadowing(string commandNames throw PSTraceSource.NewArgumentNullException(nameof(commandNames)); } - string errorId = "ErrorCommandSkippedBecauseOfShadowing"; + const string errorId = "ErrorCommandSkippedBecauseOfShadowing"; ErrorDetails details = this.GetErrorDetails(errorId, commandNames); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidData, @@ -608,11 +619,11 @@ private ErrorRecord GetErrorSkippedNonRequestedCommand(string commandName) throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } - string errorId = "ErrorSkippedNonRequestedCommand"; + const string errorId = "ErrorSkippedNonRequestedCommand"; ErrorDetails details = this.GetErrorDetails(errorId, commandName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.ResourceExists, @@ -629,11 +640,11 @@ private ErrorRecord GetErrorSkippedNonRequestedTypeDefinition(string typeName) throw PSTraceSource.NewArgumentNullException(nameof(typeName)); } - string errorId = "ErrorSkippedNonRequestedTypeDefinition"; + const string errorId = "ErrorSkippedNonRequestedTypeDefinition"; ErrorDetails details = this.GetErrorDetails(errorId, typeName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.ResourceExists, @@ -650,11 +661,11 @@ private ErrorRecord GetErrorSkippedUnsafeCommandName(string commandName) throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } - string errorId = "ErrorSkippedUnsafeCommandName"; + const string errorId = "ErrorSkippedUnsafeCommandName"; ErrorDetails details = this.GetErrorDetails(errorId, commandName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidData, @@ -686,7 +697,7 @@ private ErrorRecord GetErrorSkippedUnsafeNameInMetadata(string commandName, stri ErrorDetails details = this.GetErrorDetails(errorId, commandName, name); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidData, @@ -715,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))) { @@ -757,11 +767,11 @@ private ErrorRecord GetErrorCouldntResolvedAlias(string aliasName) throw PSTraceSource.NewArgumentNullException(nameof(aliasName)); } - string errorId = "ErrorCouldntResolveAlias"; + const string errorId = "ErrorCouldntResolveAlias"; ErrorDetails details = this.GetErrorDetails(errorId, aliasName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(details.Message), errorId, ErrorCategory.OperationTimeout, @@ -778,11 +788,11 @@ private ErrorRecord GetErrorNoResultsFromRemoteEnd(string commandName) throw PSTraceSource.NewArgumentNullException(nameof(commandName)); } - string errorId = "ErrorNoResultsFromRemoteEnd"; + const string errorId = "ErrorNoResultsFromRemoteEnd"; ErrorDetails details = this.GetErrorDetails(errorId, commandName); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(details.Message), errorId, ErrorCategory.InvalidResult, @@ -792,7 +802,7 @@ private ErrorRecord GetErrorNoResultsFromRemoteEnd(string commandName) return errorRecord; } - private List _commandsSkippedBecauseOfShadowing = new List(); + private readonly List _commandsSkippedBecauseOfShadowing = new(); private void ReportSkippedCommands() { @@ -833,7 +843,7 @@ private Dictionary ExistingCommands if (_existingCommands == null) { _existingCommands = new Dictionary(StringComparer.OrdinalIgnoreCase); - CommandSearcher searcher = new CommandSearcher( + CommandSearcher searcher = new( "*", SearchResolutionOptions.CommandNameIsPattern | SearchResolutionOptions.ResolveAliasPatterns | SearchResolutionOptions.ResolveFunctionPatterns, CommandTypes.All, @@ -852,7 +862,7 @@ private bool IsShadowingExistingCommands(string commandName) { commandName = ModuleCmdletBase.AddPrefixToCommandName(commandName, this.Prefix); - CommandSearcher searcher = new CommandSearcher(commandName, SearchResolutionOptions.None, CommandTypes.All, this.Context); + CommandSearcher searcher = new(commandName, SearchResolutionOptions.None, CommandTypes.All, this.Context); foreach (string expandedCommandName in searcher.ConstructSearchPatternsFromName(commandName)) { if (this.ExistingCommands.ContainsKey(expandedCommandName)) @@ -1030,10 +1040,7 @@ private List RehydrateList(string commandName, PSObject deserializedObject private List RehydrateList(string commandName, object deserializedList, Func itemRehydrator) { - if (itemRehydrator == null) - { - itemRehydrator = delegate (PSObject pso) { return ConvertTo(commandName, pso); }; - } + itemRehydrator ??= (PSObject pso) => ConvertTo(commandName, pso); List result = null; @@ -1052,17 +1059,18 @@ private List RehydrateList(string commandName, object deserializedList, Fu return result; } - private Dictionary RehydrateDictionary(string commandName, PSObject deserializedObject, string propertyName, Func valueRehydrator) + private Dictionary RehydrateDictionary( + string commandName, + 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 = delegate (PSObject pso) { return ConvertTo(commandName, pso); }; - } + valueRehydrator ??= (PSObject pso) => ConvertTo(commandName, pso); - Dictionary result = new Dictionary(); + Dictionary result = new(); PSPropertyInfo deserializedDictionaryProperty = deserializedObject.Properties[propertyName]; if (deserializedDictionaryProperty != null) { @@ -1071,10 +1079,10 @@ private Dictionary RehydrateDictionary(string commandName, PSObject { foreach (DictionaryEntry deserializedItem in deserializedDictionary) { - K itemKey = ConvertTo(commandName, deserializedItem.Key); + TKey itemKey = ConvertTo(commandName, deserializedItem.Key); PSObject deserializedItemValue = ConvertTo(commandName, deserializedItem.Value); - V itemValue = valueRehydrator(deserializedItemValue); + TValue itemValue = valueRehydrator(deserializedItemValue); result.Add(itemKey, itemValue); } @@ -1093,7 +1101,7 @@ private Dictionary RehydrateDictionary(string commandName, PSObject /// (i.e. it can't be used for code injection attacks). /// /// Name to validate. - /// true if the name is safe; false otherwise. + /// if the name is safe; otherwise. private static bool IsSafeNameOrIdentifier(string name) { // '.' is needed for stuff like net.exe @@ -1111,10 +1119,10 @@ private static bool IsSafeNameOrIdentifier(string name) /// (i.e. it can't be used for code injection attacks). /// /// Parameter name to validate. - /// true if the name is safe; false otherwise. + /// if the name is safe; otherwise. private static bool IsSafeParameterName(string parameterName) { - return IsSafeNameOrIdentifier(parameterName) && !parameterName.Contains(":"); + return IsSafeNameOrIdentifier(parameterName) && !parameterName.Contains(':'); } /// @@ -1122,7 +1130,7 @@ private static bool IsSafeParameterName(string parameterName) /// (i.e. it doesn't introduce any side effects on the client). /// /// Type to validate. - /// true if the type is safe; false otherwise. + /// if the type is safe; otherwise. private static bool IsSafeTypeConstraint(Type type) { if (type == null) @@ -1165,7 +1173,7 @@ private static bool IsSafeTypeConstraint(Type type) /// Writes error messages if necessary. Modifies command metadata to make it safe if necessary. /// /// Command metadata to verify. - /// true if the command metadata is safe; false otherwise. + /// if the command metadata is safe; otherwise. private bool IsSafeCommandMetadata(CommandMetadata commandMetadata) { if (!IsCommandNameMatchingParameters(commandMetadata.Name)) @@ -1191,7 +1199,7 @@ private bool IsSafeCommandMetadata(CommandMetadata commandMetadata) } Dbg.Assert(commandMetadata.CommandType == null, "CommandType shouldn't get rehydrated"); - Dbg.Assert(commandMetadata.ImplementsDynamicParameters == false, "Proxies shouldn't do dynamic parameters"); + Dbg.Assert(!commandMetadata.ImplementsDynamicParameters, "Proxies shouldn't do dynamic parameters"); if (commandMetadata.Parameters != null) { @@ -1280,8 +1288,8 @@ private ParameterMetadata RehydrateParameterMetadata(PSObject deserializedParame Type parameterType = RehydrateParameterType(deserializedParameterMetadata); List aliases = RehydrateList("Get-Command", deserializedParameterMetadata, "Aliases", null); - ParameterSetMetadata parameterSetMetadata = new ParameterSetMetadata(int.MinValue, 0, null); - Dictionary parameterSets = new Dictionary(StringComparer.OrdinalIgnoreCase); + ParameterSetMetadata parameterSetMetadata = new(int.MinValue, 0, null); + Dictionary parameterSets = new(StringComparer.OrdinalIgnoreCase); parameterSets.Add(ParameterAttribute.AllParameterSets, parameterSetMetadata); return new ParameterMetadata( @@ -1297,8 +1305,18 @@ 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; @@ -1335,13 +1353,13 @@ private CommandMetadata RehydrateCommandMetadata(PSObject deserializedCommandInf // add client-side AsJob parameter parameters.Remove("AsJob"); - ParameterMetadata asJobParameter = new ParameterMetadata("AsJob", typeof(SwitchParameter)); + ParameterMetadata asJobParameter = new("AsJob", typeof(SwitchParameter)); parameters.Add(asJobParameter.Name, asJobParameter); return new CommandMetadata( name: name, commandType: commandType, - isProxyForCmdlet: this.IsProxyForCmdlet(parameters), + isProxyForCmdlet: IsProxyForCmdlet(parameters), defaultParameterSetName: ParameterAttribute.AllParameterSets, supportsShouldProcess: false, confirmImpact: ConfirmImpact.None, @@ -1351,7 +1369,7 @@ private CommandMetadata RehydrateCommandMetadata(PSObject deserializedCommandInf parameters: parameters); } - private int GetCommandTypePriority(CommandTypes commandType) + private static int GetCommandTypePriority(CommandTypes commandType) { switch (commandType) { @@ -1420,8 +1438,8 @@ private void AddRemoteCommandMetadata( CommandMetadata previousCommandWithSameName; if (name2commandMetadata.TryGetValue(commandMetadata.Name, out previousCommandWithSameName)) { - int previousCommandPriority = this.GetCommandTypePriority(previousCommandWithSameName.WrappedCommandType); - int currentCommandPriority = this.GetCommandTypePriority(commandMetadata.WrappedCommandType); + int previousCommandPriority = GetCommandTypePriority(previousCommandWithSameName.WrappedCommandType); + int currentCommandPriority = GetCommandTypePriority(commandMetadata.WrappedCommandType); if (previousCommandPriority < currentCommandPriority) { return; @@ -1566,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); @@ -1608,12 +1625,12 @@ internal List GetRemoteFormatData() { DateTime startTime = DateTime.UtcNow; - PSDataCollection asyncOutput = new PSDataCollection(); + PSDataCollection asyncOutput = new(); // process output and errors as soon as possible asyncResult = powerShell.BeginInvoke(null, asyncOutput); int numberOfReceivedObjects = 0; - List result = new List(); + List result = new(); foreach (PSObject deserializedFormatData in asyncOutput) { AddRemoteTypeDefinition(result, deserializedFormatData); @@ -1668,7 +1685,7 @@ private PowerShell BuildPowerShellForGetCommand() powerShell.AddParameter("ArgumentList", this.ArgumentList); powerShell.Runspace = Session.Runspace; - powerShell.RemotePowerShell.HostCallReceived += new EventHandler>(HandleHostCallReceived); + powerShell.RemotePowerShell.HostCallReceived += HandleHostCallReceived; return powerShell; } @@ -1688,7 +1705,7 @@ private void HandleHostCallReceived(object sender, RemoteDataEventArgs GetRemoteCommandMetadata(out Dictionary alias2resolvedCommandName) { bool isReleaseCandidateBackcompatibilityMode = - this.Session.Runspace.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersionWin7RC; + this.Session.Runspace.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersion_2_0; alias2resolvedCommandName = new Dictionary(StringComparer.OrdinalIgnoreCase); if ((this.CommandName == null) || (this.CommandName.Length == 0) || @@ -1719,14 +1736,14 @@ internal List GetRemoteCommandMetadata(out Dictionary name2commandMetadata = - new Dictionary(StringComparer.OrdinalIgnoreCase); + new(StringComparer.OrdinalIgnoreCase); // invoke using (new PowerShellStopper(this.Context, powerShell)) { DateTime startTime = DateTime.UtcNow; - PSDataCollection asyncOutput = new PSDataCollection(); + PSDataCollection asyncOutput = new(); // process output and errors as soon as possible asyncResult = powerShell.BeginInvoke(null, asyncOutput); @@ -1801,7 +1818,7 @@ private void WriteProgress(string statusDescription, int? percentComplete, int? _lastTimeProgressWasWritten = DateTime.UtcNow; string activityDescription = StringUtil.Format(ImplicitRemotingStrings.ProgressActivity); - ProgressRecord progressRecord = new ProgressRecord( + ProgressRecord progressRecord = new( 1905347799, // unique id for ImplicitRemoting (I just picked a random number) activityDescription, statusDescription); @@ -1876,7 +1893,7 @@ internal List GenerateProxyModule( } } - ImplicitRemotingCodeGenerator codeGenerator = new ImplicitRemotingCodeGenerator( + ImplicitRemotingCodeGenerator codeGenerator = new( this.Session, this.ModuleGuid, this.MyInvocation); @@ -1899,15 +1916,15 @@ internal List GenerateProxyModule( #endregion } - internal class ImplicitRemotingCodeGenerator + internal sealed class ImplicitRemotingCodeGenerator { - internal static readonly Version VersionOfScriptWriter = new Version(1, 0); + internal static readonly Version VersionOfScriptWriter = new(1, 0); #region Constructor and shared private data - private PSSession _remoteRunspaceInfo; - private Guid _moduleGuid; - private InvocationInfo _invocationInfo; + private readonly PSSession _remoteRunspaceInfo; + private readonly Guid _moduleGuid; + private readonly InvocationInfo _invocationInfo; internal ImplicitRemotingCodeGenerator( PSSession remoteRunspaceInfo, @@ -1915,7 +1932,7 @@ internal ImplicitRemotingCodeGenerator( InvocationInfo invocationInfo) { Dbg.Assert(remoteRunspaceInfo != null, "Caller should validate remoteRunspaceInfo != null"); - Dbg.Assert(moduleGuid != null, "Caller should validate moduleGuid != null"); + Dbg.Assert(moduleGuid != Guid.Empty, "Caller should validate moduleGuid is not empty"); Dbg.Assert(invocationInfo != null, "Caller should validate invocationInfo != null"); _remoteRunspaceInfo = remoteRunspaceInfo; @@ -1933,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; } @@ -1961,19 +1975,19 @@ private string GetConnectionString() return null; } - private string EscapeFunctionNameForRemoteHelp(string name) + private static string EscapeFunctionNameForRemoteHelp(string name) { if (name == null) { throw PSTraceSource.NewArgumentNullException(nameof(name)); } - StringBuilder result = new StringBuilder(name.Length); + StringBuilder result = new(name.Length); foreach (char c in name) { - if (("\"'`$".IndexOf(c) == (-1)) && - (!char.IsControl(c)) && - (!char.IsWhiteSpace(c))) + if (!"\"'`$".Contains(c) + && !char.IsControl(c) + && !char.IsWhiteSpace(c)) { result.Append(c); } @@ -1986,7 +2000,7 @@ private string EscapeFunctionNameForRemoteHelp(string name) ############################################################################## "; - private void GenerateSectionSeparator(TextWriter writer) + private static void GenerateSectionSeparator(TextWriter writer) { writer.Write(SectionSeparator); } @@ -2006,7 +2020,6 @@ private void GenerateSectionSeparator(TextWriter writer) PrivateData = @{{ ImplicitRemoting = $true - ImplicitSessionId = '{4}' }} }} "; @@ -2025,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 @@ -2093,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, @@ -2121,7 +2135,7 @@ function Write-PSImplicitRemotingMessage } "; - private void GenerateHelperFunctionsWriteMessage(TextWriter writer) + private static void GenerateHelperFunctionsWriteMessage(TextWriter writer) { if (writer == null) { @@ -2176,7 +2190,7 @@ function Set-PSImplicitRemotingSession if ($PSSessionOverride) {{ Set-PSImplicitRemotingSession $PSSessionOverride }} "; - private void GenerateHelperFunctionsSetImplicitRunspace(TextWriter writer) + private static void GenerateHelperFunctionsSetImplicitRunspace(TextWriter writer) { if (writer == null) { @@ -2233,66 +2247,70 @@ private PSPrimitiveDictionary GetApplicationArguments() private string GenerateNewPSSessionOption() { - StringBuilder result = new StringBuilder("& $script:NewPSSessionOption "); + 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)); } @@ -2302,7 +2320,7 @@ private string GenerateNewPSSessionOption() result.Append("-ApplicationArguments $("); result.Append("& $script:ImportCliXml -Path $("); result.Append("& $script:JoinPath -Path $PSScriptRoot -ChildPath ApplicationArguments.xml"); - result.Append(")"); + result.Append(')'); result.Append(") "); } @@ -2416,7 +2434,7 @@ private void GenerateHelperFunctionsGetImplicitRunspace(TextWriter writer) out hashString, ImplicitRemotingCommandBase.ImplicitRemotingKey, ImplicitRemotingCommandBase.ImplicitRemotingHashKey); - hashString = hashString ?? string.Empty; + hashString ??= string.Empty; writer.Write( HelperFunctionsGetImplicitRunspaceTemplate, @@ -2441,7 +2459,7 @@ private void GenerateHelperFunctionsGetImplicitRunspace(TextWriter writer) private string GenerateReimportingOfModules() { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); if (_invocationInfo.BoundParameters.ContainsKey(nameof(Module))) { @@ -2507,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( @@ -2520,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( @@ -2563,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, @@ -2595,22 +2608,19 @@ 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}'"); } } private string GenerateAllowRedirectionParameter() { - WSManConnectionInfo wsmanConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (wsmanConnectionInfo == null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } @@ -2636,8 +2646,7 @@ private string GenerateAuthenticationMechanismParameter() return string.Empty; } - WSManConnectionInfo wsmanConnectionInfo = _remoteRunspaceInfo.Runspace.ConnectionInfo as WSManConnectionInfo; - if (wsmanConnectionInfo == null) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } @@ -2733,7 +2742,7 @@ function Get-PSImplicitRemotingClientSideParameters $clientSideParameters = @{} - $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction' + $parametersToLeaveRemote = 'ErrorAction', 'WarningAction', 'InformationAction', 'ProgressAction' Modify-PSImplicitRemotingParameters $clientSideParameters $PSBoundParameters 'AsJob' if ($proxyForCmdlet) @@ -2755,7 +2764,7 @@ function Get-PSImplicitRemotingClientSideParameters } "; - private void GenerateHelperFunctionsClientSideParameters(TextWriter writer) + private static void GenerateHelperFunctionsClientSideParameters(TextWriter writer) { if (writer == null) { @@ -2769,12 +2778,12 @@ private void GenerateHelperFunctionsClientSideParameters(TextWriter writer) private void GenerateHelperFunctions(TextWriter writer) { - this.GenerateSectionSeparator(writer); - this.GenerateHelperFunctionsWriteMessage(writer); + GenerateSectionSeparator(writer); + GenerateHelperFunctionsWriteMessage(writer); this.GenerateHelperFunctionsGetSessionOption(writer); - this.GenerateHelperFunctionsSetImplicitRunspace(writer); + GenerateHelperFunctionsSetImplicitRunspace(writer); this.GenerateHelperFunctionsGetImplicitRunspace(writer); - this.GenerateHelperFunctionsClientSideParameters(writer); + GenerateHelperFunctionsClientSideParameters(writer); } #endregion @@ -2809,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) @@ -2834,7 +2844,7 @@ private void GenerateHelperFunctions(TextWriter writer) }} "; - private void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMetadata) + private static void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMetadata) { if (writer == null) { @@ -2842,7 +2852,7 @@ private void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMeta } string functionNameForString = CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name); - string functionNameForHelp = this.EscapeFunctionNameForRemoteHelp(commandMetadata.Name); + string functionNameForHelp = EscapeFunctionNameForRemoteHelp(commandMetadata.Name); writer.Write( CommandProxyTemplate, /* 0 */ functionNameForString, @@ -2856,7 +2866,7 @@ private void GenerateCommandProxy(TextWriter writer, CommandMetadata commandMeta /* 8 */ commandMetadata.WrappedAnyCmdlet); } - private void GenerateCommandProxy(TextWriter writer, IEnumerable listOfCommandMetadata) + private static void GenerateCommandProxy(TextWriter writer, IEnumerable listOfCommandMetadata) { if (writer == null) { @@ -2868,7 +2878,7 @@ private void GenerateCommandProxy(TextWriter writer, IEnumerable listOfCommandMetadata) + private static void GenerateExportDeclaration(TextWriter writer, IEnumerable listOfCommandMetadata) { if (writer == null) { @@ -2895,21 +2905,21 @@ private void GenerateExportDeclaration(TextWriter writer, IEnumerable listOfCommandNames = GetListOfCommandNames(listOfCommandMetadata); string exportString = GenerateArrayString(listOfCommandNames); writer.Write(ExportFunctionsTemplate, exportString); } - private List GetListOfCommandNames(IEnumerable listOfCommandMetadata) + private static List GetListOfCommandNames(IEnumerable listOfCommandMetadata) { if (listOfCommandMetadata == null) { throw PSTraceSource.NewArgumentNullException(nameof(listOfCommandMetadata)); } - List listOfCommandNames = new List(); + List listOfCommandNames = new(); foreach (CommandMetadata commandMetadata in listOfCommandMetadata) { listOfCommandNames.Add(commandMetadata.Name); @@ -2918,14 +2928,14 @@ private List GetListOfCommandNames(IEnumerable listOfCo return listOfCommandNames; } - private string GenerateArrayString(IEnumerable listOfStrings) + private static string GenerateArrayString(IEnumerable listOfStrings) { if (listOfStrings == null) { throw PSTraceSource.NewArgumentNullException(nameof(listOfStrings)); } - StringBuilder arrayString = new StringBuilder(); + StringBuilder arrayString = new(); foreach (string s in listOfStrings) { if (arrayString.Length != 0) @@ -2939,7 +2949,7 @@ private string GenerateArrayString(IEnumerable listOfStrings) } arrayString.Insert(0, "@("); - arrayString.Append(")"); + arrayString.Append(')'); return arrayString.ToString(); } @@ -2956,9 +2966,9 @@ private string GenerateArrayString(IEnumerable listOfStrings) & $script:ExportModuleMember -Alias {0} "; - private void GenerateAliases(TextWriter writer, Dictionary alias2resolvedCommandName) + private static void GenerateAliases(TextWriter writer, Dictionary alias2resolvedCommandName) { - this.GenerateSectionSeparator(writer); + GenerateSectionSeparator(writer); foreach (KeyValuePair pair in alias2resolvedCommandName) { @@ -2979,7 +2989,7 @@ private void GenerateAliases(TextWriter writer, Dictionary alias #region Generating format.ps1xml file - private void GenerateFormatFile(TextWriter writer, List listOfFormatData) + private static void GenerateFormatFile(TextWriter writer, List listOfFormatData) { if (writer == null) { @@ -2991,7 +3001,7 @@ private void GenerateFormatFile(TextWriter writer, List throw PSTraceSource.NewArgumentNullException(nameof(listOfFormatData)); } - XmlWriterSettings settings = new XmlWriterSettings(); + XmlWriterSettings settings = new(); settings.CloseOutput = false; settings.ConformanceLevel = ConformanceLevel.Document; settings.Encoding = writer.Encoding; @@ -3026,7 +3036,7 @@ internal List GenerateProxyModule( List listOfFormatData, X509Certificate2 certificate) { - List result = new List(); + List result = new(); Dbg.Assert(moduleRootDirectory != null, "Caller should validate moduleRootDirectory != null"); Dbg.Assert(Directory.Exists(moduleRootDirectory.FullName), "Caller should validate moduleRootDirectory exists"); @@ -3036,17 +3046,14 @@ internal List GenerateProxyModule( FileMode fileMode = force ? FileMode.OpenOrCreate : FileMode.CreateNew; result.Add(baseName + ".psm1"); - FileStream psm1 = new FileStream( + FileStream psm1 = new( baseName + ".psm1", fileMode, FileAccess.Write, FileShare.None); using (TextWriter writer = new StreamWriter(psm1, encoding)) { - if (listOfCommandMetadata == null) - { - listOfCommandMetadata = new List(); - } + listOfCommandMetadata ??= new List(); GenerateModuleHeader(writer); GenerateHelperFunctions(writer); @@ -3057,17 +3064,14 @@ internal List GenerateProxyModule( } result.Add(baseName + ".format.ps1xml"); - FileStream formatPs1xml = new FileStream( + FileStream formatPs1xml = new( baseName + ".format.ps1xml", fileMode, FileAccess.Write, 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); @@ -3102,8 +3106,8 @@ internal List GenerateProxyModule( } result.Add(baseName + ".psd1"); - FileInfo manifestFile = new FileInfo(baseName + ".psd1"); - FileStream psd1 = new FileStream( + FileInfo manifestFile = new(baseName + ".psd1"); + FileStream psd1 = new( manifestFile.FullName, fileMode, FileAccess.Write, @@ -3123,7 +3127,7 @@ internal List GenerateProxyModule( using (var stream = new FileStream(applicationArgumentsFile, FileMode.Create, FileAccess.Write, FileShare.Read)) using (var xmlWriter = XmlWriter.Create(stream)) { - Serializer serializer = new Serializer(xmlWriter); + Serializer serializer = new(xmlWriter); serializer.Serialize(applicationArguments); serializer.Done(); } 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 4749d28322c..c4e423ffd1d 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); @@ -186,7 +195,7 @@ protected override void ProcessRecord() if (_bindingVariable != null) { - VariablePath variablePath = new VariablePath(_bindingVariable); + VariablePath variablePath = new(_bindingVariable); if (variablePath.IsUnscopedVariable) { variablePath = variablePath.CloneAndSetLocal(); @@ -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); @@ -340,8 +363,8 @@ private string GetScript(string filePath) // 197751: WR BUG BASH: Powershell: localized text display as garbage // leaving the encoding to be decided by the StreamReader. StreamReader // will read the preamble and decide proper encoding. - using (FileStream scriptStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (StreamReader scriptReader = new StreamReader(scriptStream)) + using (FileStream scriptStream = new(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (StreamReader scriptReader = new(scriptStream)) { return scriptReader.ReadToEnd(); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportAliasCommand.cs index 3da62e88d25..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; } /// @@ -154,7 +155,7 @@ protected override void ProcessRecord() // Since the alias already exists, write an error. SessionStateException aliasExists = - new SessionStateException( + new( alias.Name, SessionStateCategory.Alias, "AliasAlreadyExists", @@ -233,7 +234,7 @@ private Dictionary ExistingCommands if (_existingCommands == null) { _existingCommands = new Dictionary(StringComparer.OrdinalIgnoreCase); - CommandSearcher searcher = new CommandSearcher( + CommandSearcher searcher = new( "*", SearchResolutionOptions.CommandNameIsPattern | SearchResolutionOptions.ResolveAliasPatterns | SearchResolutionOptions.ResolveFunctionPatterns, CommandTypes.All ^ CommandTypes.Alias, @@ -260,7 +261,7 @@ private Dictionary ExistingCommands private bool VerifyShadowingExistingCommandsAndWriteError(string aliasName) { - CommandSearcher searcher = new CommandSearcher(aliasName, SearchResolutionOptions.None, CommandTypes.All ^ CommandTypes.Alias, this.Context); + CommandSearcher searcher = new(aliasName, SearchResolutionOptions.None, CommandTypes.All ^ CommandTypes.Alias, this.Context); foreach (string expandedCommandName in searcher.ConstructSearchPatternsFromName(aliasName)) { CommandTypes commandTypeOfExistingCommand; @@ -268,7 +269,7 @@ private bool VerifyShadowingExistingCommandsAndWriteError(string aliasName) { // Since the alias already exists, write an error. SessionStateException aliasExists = - new SessionStateException( + new( aliasName, SessionStateCategory.Alias, "AliasAlreadyExists", @@ -289,14 +290,14 @@ private bool VerifyShadowingExistingCommandsAndWriteError(string aliasName) private Collection GetAliasesFromFile(bool isLiteralPath) { - Collection result = new Collection(); + Collection result = new(); string filePath = null; using (StreamReader reader = OpenFile(out filePath, isLiteralPath)) { - CSVHelper csvHelper = new CSVHelper(','); + CSVHelper csvHelper = new(','); - Int64 lineNumber = 0; + long lineNumber = 0; string line = null; while ((line = reader.ReadLine()) != null) { @@ -327,10 +328,10 @@ private Collection GetAliasesFromFile(bool isLiteralPath) string message = StringUtil.Format(AliasCommandStrings.ImportAliasFileInvalidFormat, filePath, lineNumber); FormatException formatException = - new FormatException(message); + new(message); ErrorRecord errorRecord = - new ErrorRecord( + new( formatException, "ImportAliasFileFormatError", ErrorCategory.ReadError, @@ -352,7 +353,7 @@ private Collection GetAliasesFromFile(bool isLiteralPath) string message = StringUtil.Format(AliasCommandStrings.ImportAliasOptionsError, filePath, lineNumber); ErrorRecord errorRecord = - new ErrorRecord( + new( argException, "ImportAliasOptionsError", ErrorCategory.ReadError, @@ -364,7 +365,7 @@ private Collection GetAliasesFromFile(bool isLiteralPath) } AliasInfo newAlias = - new AliasInfo( + new( values[0], values[1], Context, @@ -427,7 +428,7 @@ private StreamReader OpenFile(out string filePath, bool isLiteralPath) try { - FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + FileStream file = new(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); result = new StreamReader(file); } catch (IOException ioException) @@ -451,7 +452,7 @@ private void ThrowFileOpenError(Exception e, string pathWithError) string message = StringUtil.Format(AliasCommandStrings.ImportAliasFileOpenFailed, pathWithError, e.Message); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( e, "FileOpenFailure", ErrorCategory.OpenError, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs index 694e82686be..5661df24fa9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImportPowerShellDataFile.cs @@ -39,6 +39,12 @@ public string[] LiteralPath set { _isLiteralPath = true; Path = value; } } + /// + /// Gets or sets switch that determines if built-in limits are applied to the data. + /// + [Parameter] + public SwitchParameter SkipLimitCheck { get; set; } + /// /// For each path, resolve it, parse it and write all hashtables to the output stream. /// @@ -58,10 +64,10 @@ 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()); + WriteObject(data.SafeGetValue(SkipLimitCheck)); } else { @@ -78,8 +84,8 @@ protected override void ProcessRecord() private void WritePathNotFoundError(string path) { - var errorId = "PathNotFound"; - var errorCategory = ErrorCategory.InvalidArgument; + const string errorId = "PathNotFound"; + const ErrorCategory errorCategory = ErrorCategory.InvalidArgument; var errorMessage = string.Format(UtilityCommonStrings.PathDoesNotExist, path); var exception = new ArgumentException(errorMessage); var errorRecord = new ErrorRecord(exception, errorId, errorCategory, path); @@ -88,7 +94,7 @@ private void WritePathNotFoundError(string path) private void WriteInvalidDataFileError(string resolvedPath, string errorId) { - var errorCategory = ErrorCategory.InvalidData; + const ErrorCategory errorCategory = ErrorCategory.InvalidData; var errorMessage = string.Format(UtilityCommonStrings.CouldNotParseAsPowerShellDataFile, resolvedPath); var exception = new InvalidOperationException(errorMessage); var errorRecord = new ErrorRecord(exception, errorId, errorCategory, resolvedPath); 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 652e32652ce..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; @@ -23,7 +24,7 @@ public sealed class JoinStringCommand : PSCmdlet /// A bigger default to not get re-allocations in common use cases. private const int DefaultOutputStringCapacity = 256; - private readonly StringBuilder _outputBuilder = new StringBuilder(DefaultOutputStringCapacity); + private readonly StringBuilder _outputBuilder = new(DefaultOutputStringCapacity); private CultureInfo _cultureInfo = CultureInfo.InvariantCulture; private string _separator; private char _quoteChar; @@ -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; } /// @@ -93,7 +94,7 @@ public string Separator [Parameter(ValueFromPipeline = true)] public PSObject[] InputObject { get; set; } - /// + /// protected override void BeginProcessing() { _quoteChar = SingleQuote ? '\'' : DoubleQuote ? '"' : char.MinValue; @@ -104,7 +105,7 @@ protected override void BeginProcessing() } } - /// + /// protected override void ProcessRecord() { if (InputObject != null) @@ -151,7 +152,7 @@ protected override void ProcessRecord() } } - /// + /// protected override void EndProcessing() { _outputBuilder.Append(OutputSuffix); @@ -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 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..7c2a7ac65f4 --- /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 sealed 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/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index 0ee80712123..cd18f33c41d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -3,13 +3,9 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; -using System.Threading.Tasks; using Microsoft.PowerShell.MarkdownRender; @@ -159,7 +155,7 @@ protected override void EndProcessing() { var errorMessage = StringUtil.Format(ConvertMarkdownStrings.InvalidInputObjectType, baseObj.GetType()); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(errorMessage), "InvalidObject", ErrorCategory.InvalidArgument, @@ -271,7 +267,7 @@ protected override void EndProcessing() /// internal static class PSMarkdownOptionInfoCache { - private static ConcurrentDictionary markdownOptionInfoCache; + private static readonly ConcurrentDictionary markdownOptionInfoCache; private const string MarkdownOptionInfoVariableName = "PSMarkdownOptionInfo"; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index 3a5d4c77a5f..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 { @@ -142,7 +142,7 @@ public string Filename return s_inputStream; } - return _filename ?? (_filename = System.IO.Path.GetFileName(_path)); + return _filename ??= System.IO.Path.GetFileName(_path); } } @@ -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() { @@ -275,9 +275,9 @@ private string ToString(string directory, string line) } // Otherwise, render the full context. - List lines = new List(Context.DisplayPreContext.Length + Context.DisplayPostContext.Length + 1); + 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) @@ -366,7 +366,7 @@ private string FormatLine(string lineStr, int displayLineNumber, string displayP /// /// Gets or sets a list of all Regex matches on the matching line. /// - public Match[] Matches { get; set; } = new Match[] { }; + public Match[] Matches { get; set; } = Array.Empty(); /// /// Create a deep copy of this MatchInfo instance. @@ -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; @@ -425,13 +425,10 @@ private class CircularBuffer : ICollection /// Initializes a new instance of the class. /// /// The maximum capacity of the buffer. - /// If is negative. + /// If is negative. public CircularBuffer(int capacity) { - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity)); - } + ArgumentOutOfRangeException.ThrowIfNegative(capacity); _items = new T[capacity]; Clear(); @@ -455,9 +452,9 @@ public CircularBuffer(int capacity) /// has been properly offset and wrapped. /// /// The index to wrap. - /// If is out of range. + /// If is out of range. /// - /// The actual index that + /// The actual index that /// maps to. /// private int WrapIndex(int zeroBasedIndex) @@ -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; @@ -851,14 +841,14 @@ public LogicalContextTracker(int preContext, int postContext) public void TrackLine(string line) { - ContextEntry entry = new ContextEntry(line); + ContextEntry entry = new(line); _collectedContext.Add(entry); UpdateQueue(); } public void TrackMatch(MatchInfo match) { - ContextEntry entry = new ContextEntry(match); + ContextEntry entry = new(match); _collectedContext.Add(entry); UpdateQueue(); } @@ -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,10 +1336,24 @@ public string[] Exclude /// Gets or sets the text encoding to process each file as. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; /// /// Gets or sets the number of context lines to collect. If set to a @@ -1362,7 +1366,7 @@ public string[] Exclude [Parameter] [ValidateNotNullOrEmpty] [ValidateCount(1, 2)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public new int[] Context { get => _context; @@ -1413,7 +1417,7 @@ private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _pos /// private bool _doneProcessing; - private int _inputRecordNumber; + private ulong _inputRecordNumber; /// /// Read command line parameters. @@ -1422,8 +1426,8 @@ protected override void BeginProcessing() { if (this.MyInvocation.BoundParameters.ContainsKey(nameof(Culture)) && !this.MyInvocation.BoundParameters.ContainsKey(nameof(SimpleMatch))) { - InvalidOperationException exception = new InvalidOperationException(MatchStringStrings.CannotSpecifyCultureWithoutSimpleMatch); - ErrorRecord errorRecord = new ErrorRecord(exception, "CannotSpecifyCultureWithoutSimpleMatch", ErrorCategory.InvalidData, null); + InvalidOperationException exception = new(MatchStringStrings.CannotSpecifyCultureWithoutSimpleMatch); + ErrorRecord errorRecord = new(exception, "CannotSpecifyCultureWithoutSimpleMatch", ErrorCategory.InvalidData, null); this.ThrowTerminatingError(errorRecord); } @@ -1457,7 +1461,7 @@ protected override void BeginProcessing() _globalContextTracker = GetContextTracker(); } - private readonly List _inputObjectFileList = new List(1) { string.Empty }; + private readonly List _inputObjectFileList = new(1) { string.Empty }; /// /// Process the input. @@ -1586,12 +1590,12 @@ private bool ProcessFile(string filename) return false; } - using (FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + using (FileStream fs = new(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - using (StreamReader sr = new StreamReader(fs, Encoding)) + 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. @@ -1883,8 +1887,8 @@ private bool DoMatchWorker(string operandString, MatchInfo matchInfo, out MatchI if (matchInfo.Context != null) { matchResult = matchInfo.Clone(); - matchResult.Context.DisplayPreContext = new string[] { }; - matchResult.Context.DisplayPostContext = new string[] { }; + matchResult.Context.DisplayPreContext = Array.Empty(); + matchResult.Context.DisplayPostContext = Array.Empty(); } else { @@ -1910,7 +1914,7 @@ private bool DoMatchWorker(string operandString, MatchInfo matchInfo, out MatchI // Matches should be an empty list, rather than null, // in the cases of notMatch and simpleMatch. - matchResult.Matches = matches ?? new Match[] { }; + matchResult.Matches = matches ?? Array.Empty(); return true; } @@ -1926,7 +1930,7 @@ private bool DoMatchWorker(string operandString, MatchInfo matchInfo, out MatchI /// The resolved (absolute) paths. private List ResolveFilePaths(string[] filePaths, bool isLiteralPath) { - List allPaths = new List(); + List allPaths = new(); foreach (string path in filePaths) { @@ -1969,7 +1973,7 @@ private static ErrorRecord BuildErrorRecord(string messageId, string arg0, strin private static ErrorRecord BuildErrorRecord(string messageId, object[] arguments, string errorId, Exception innerException) { string fmtedMsg = StringUtil.Format(messageId, arguments); - ArgumentException e = new ArgumentException(fmtedMsg, innerException); + ArgumentException e = new(fmtedMsg, innerException); return new ErrorRecord(e, errorId, ErrorCategory.InvalidArgument, null); } @@ -1982,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 67a9e43c3b9..1e741758270 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -8,8 +8,6 @@ using System.Management.Automation; using System.Management.Automation.Internal; -using Microsoft.PowerShell.Commands.Internal.Format; - namespace Microsoft.PowerShell.Commands { /// @@ -20,7 +18,7 @@ public abstract class MeasureInfo /// /// Property name. /// - public string Property { get; set; } = null; + public string Property { get; set; } } /// @@ -29,7 +27,7 @@ public abstract class MeasureInfo public sealed class GenericMeasureInfo : MeasureInfo { /// - /// Default ctor. + /// Initializes a new instance of the class. /// public GenericMeasureInfo() { @@ -79,6 +77,7 @@ public GenericMeasureInfo() public sealed class GenericObjectMeasureInfo : MeasureInfo { /// + /// Initializes a new instance of the class. /// Default ctor. /// public GenericObjectMeasureInfo() @@ -124,6 +123,7 @@ public GenericObjectMeasureInfo() public sealed class TextMeasureInfo : MeasureInfo { /// + /// Initializes a new instance of the class. /// Default ctor. /// public TextMeasureInfo() @@ -159,11 +159,12 @@ public sealed class MeasureObjectCommand : PSCmdlet /// Dictionary to be used by Measure-Object implementation. /// Keys are strings. Keys are compared with OrdinalIgnoreCase. /// - /// Value type. - private class MeasureObjectDictionary : Dictionary - where V : new() + /// Value type. + private sealed class MeasureObjectDictionary : Dictionary + where TValue : new() { /// + /// Initializes a new instance of the class. /// Default ctor. /// internal MeasureObjectDictionary() : base(StringComparer.OrdinalIgnoreCase) @@ -180,12 +181,12 @@ internal MeasureObjectDictionary() : base(StringComparer.OrdinalIgnoreCase) /// /// The existing value, or a newly-created value. /// - public V EnsureEntry(string key) + public TValue EnsureEntry(string key) { - V val; + TValue val; if (!TryGetValue(key, out val)) { - val = new V(); + val = new TValue(); this[key] = val; } @@ -198,9 +199,8 @@ public V EnsureEntry(string key) /// to maintain two sets of MeasureInfo and constantly checking /// what mode we're in. /// - [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] - private class Statistics + private sealed class Statistics { // Common properties internal int count = 0; @@ -219,6 +219,7 @@ private class Statistics } /// + /// Initializes a new instance of the class. /// Default constructor. /// public MeasureObjectCommand() @@ -235,7 +236,7 @@ public MeasureObjectCommand() /// /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// Properties to be examined. @@ -243,7 +244,7 @@ public MeasureObjectCommand() /// [ValidateNotNullOrEmpty] [Parameter(Position = 0)] - public PSPropertyExpression[] Property { get; set; } = null; + public PSPropertyExpression[] Property { get; set; } #endregion Common parameters in both sets @@ -495,14 +496,14 @@ protected override void ProcessRecord() /// Analyze an object on a property-by-property basis instead /// of as a simple value. /// Side effects: Updates statistics. - /// The object to analyze. /// + /// The object to analyze. private void AnalyzeObjectProperties(PSObject inObj) { // Keep track of which properties are counted for an // input object so that repeated properties won't be // counted twice. - MeasureObjectDictionary countedProperties = new MeasureObjectDictionary(); + MeasureObjectDictionary countedProperties = new(); // First iterate over the user-specified list of // properties... @@ -555,13 +556,12 @@ private void AnalyzeObjectProperties(PSObject inObj) /// /// Analyze a value for generic/text statistics. /// Side effects: Updates statistics. May set nonNumericError. + /// /// The property this value corresponds to. /// The value to analyze. - /// private void AnalyzeValue(string propertyName, object objValue) { - if (propertyName == null) - propertyName = thisObject; + propertyName ??= thisObject; Statistics stat = _statistics.EnsureEntry(propertyName); @@ -580,7 +580,7 @@ private void AnalyzeValue(string propertyName, object objValue) if (!LanguagePrimitives.TryConvertTo(objValue, out numValue)) { _nonNumericError = true; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewInvalidOperationException(MeasureObjectStrings.NonNumericInputObject, objValue), "NonNumericInputObject", ErrorCategory.InvalidType, @@ -618,11 +618,10 @@ private void AnalyzeValue(string propertyName, object objValue) /// If true is passed in then the minimum of the two values would be returned. /// If false is passed in then maximum of the two values will be returned. /// - private object Compare(object objValue, object statMinOrMaxValue, bool isMin) + private static object Compare(object objValue, object statMinOrMaxValue, bool isMin) { object currentValue = objValue; object statValue = statMinOrMaxValue; - int factor = isMin ? 1 : -1; double temp; currentValue = ((objValue != null) && LanguagePrimitives.TryConvertTo(objValue, out temp)) ? temp : currentValue; @@ -634,13 +633,15 @@ private object Compare(object objValue, object statMinOrMaxValue, bool isMin) statValue = PSObject.AsPSObject(statValue).ToString(); } - if ((statValue == null) || - ((LanguagePrimitives.Compare(statValue, currentValue, false, CultureInfo.CurrentCulture) * factor) > 0)) + if (statValue == null) { return objValue; } - return statMinOrMaxValue; + int comparisonResult = LanguagePrimitives.Compare(statValue, currentValue, ignoreCase: false, CultureInfo.CurrentCulture); + return (isMin ? comparisonResult : -comparisonResult) > 0 + ? objValue + : statMinOrMaxValue; } /// @@ -745,9 +746,9 @@ internal static int CountLine(string inStr) /// /// Update text statistics. + /// /// The text to analyze. /// The Statistics object to update. - /// private void AnalyzeString(string strValue, Statistics stat) { if (_measureCharacters) @@ -760,9 +761,9 @@ private void AnalyzeString(string strValue, Statistics stat) /// /// Update number statistics. + /// /// The number to analyze. /// The Statistics object to update. - /// private void AnalyzeNumber(double numValue, Statistics stat) { if (_measureSum || _measureAverage || _measureStandardDeviation) @@ -789,10 +790,10 @@ private void AnalyzeNumber(double numValue, Statistics stat) private void WritePropertyNotFoundError(string propertyName, string errorId) { Diagnostics.Assert(Property != null, "no property and no InputObject should have been addressed"); - ErrorRecord errorRecord = new ErrorRecord( - PSTraceSource.NewArgumentException("Property"), + ErrorRecord errorRecord = new( + PSTraceSource.NewArgumentException(propertyName), errorId, - ErrorCategory.InvalidArgument, + ErrorCategory.ObjectNotFound, null); errorRecord.ErrorDetails = new ErrorDetails( this, "MeasureObjectStrings", "PropertyNotFound", propertyName); @@ -818,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; } @@ -851,10 +855,10 @@ protected override void EndProcessing() /// /// Create a MeasureInfo object for generic stats. - /// The statistics to use. - /// A new GenericMeasureInfo object. /// + /// The statistics to use. /// + /// A new GenericMeasureInfo object. private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGenericMeasureInfo) { double? sum = null; @@ -907,7 +911,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene if (shouldUseGenericMeasureInfo) { - GenericMeasureInfo gmi = new GenericMeasureInfo(); + GenericMeasureInfo gmi = new(); gmi.Count = stat.count; gmi.Sum = sum; gmi.Average = average; @@ -926,7 +930,7 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene } else { - GenericObjectMeasureInfo gomi = new GenericObjectMeasureInfo(); + GenericObjectMeasureInfo gomi = new(); gomi.Count = stat.count; gomi.Sum = sum; gomi.Average = average; @@ -939,12 +943,12 @@ private MeasureInfo CreateGenericMeasureInfo(Statistics stat, bool shouldUseGene /// /// Create a MeasureInfo object for text stats. + /// /// The statistics to use. /// A new TextMeasureInfo object. - /// private TextMeasureInfo CreateTextMeasureInfo(Statistics stat) { - TextMeasureInfo tmi = new TextMeasureInfo(); + TextMeasureInfo tmi = new(); if (_measureCharacters) tmi.Characters = stat.characters; @@ -960,7 +964,7 @@ private TextMeasureInfo CreateTextMeasureInfo(Statistics stat) /// The observed statistics keyed by property name. /// If Property is not set, then the key used will be the value of thisObject. /// - private MeasureObjectDictionary _statistics = new MeasureObjectDictionary(); + private readonly MeasureObjectDictionary _statistics = new(); /// /// Whether or not a numeric conversion error occurred. 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 39fd390b1ce..0ea312f2963 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs @@ -13,7 +13,9 @@ using System.Management.Automation.Security; using System.Reflection; using System.Runtime.InteropServices; +#if !UNIX using System.Threading; +#endif using Dbg = System.Management.Automation.Diagnostics; @@ -30,7 +32,7 @@ public sealed class NewObjectCommand : PSCmdlet /// the number [Parameter(ParameterSetName = netSetName, Mandatory = true, Position = 0)] [ValidateTrustedData] - public string TypeName { get; set; } = null; + public string TypeName { get; set; } #if !UNIX private Guid _comObjectClsId = Guid.Empty; @@ -39,7 +41,7 @@ public sealed class NewObjectCommand : PSCmdlet /// [Parameter(ParameterSetName = "Com", Mandatory = true, Position = 0)] [ValidateTrustedData] - public string ComObject { get; set; } = null; + public string ComObject { get; set; } #endif /// @@ -49,7 +51,7 @@ public sealed class NewObjectCommand : PSCmdlet [Parameter(ParameterSetName = netSetName, Mandatory = false, Position = 1)] [ValidateTrustedData] [Alias("Args")] - public object[] ArgumentList { get; set; } = null; + public object[] ArgumentList { get; set; } /// /// True if we should have an error when Com objects will use an interop assembly. @@ -102,7 +104,7 @@ private void CreateMemberNotFoundError(PSObject pso, DictionaryEntry property, T private void CreateMemberSetValueError(SetValueException e) { - Exception ex = new Exception(StringUtil.Format(NewObjectStrings.InvalidValue, e)); + Exception ex = new(StringUtil.Format(NewObjectStrings.InvalidValue, e)); ThrowTerminatingError( new ErrorRecord(ex, "SetValueException", ErrorCategory.InvalidData, null)); } @@ -185,14 +187,44 @@ protected override void BeginProcessing() targetObject: null)); } - if (Context.LanguageMode == PSLanguageMode.ConstrainedLanguage) + switch (Context.LanguageMode) { - if (!CoreTypes.Contains(type)) - { - ThrowTerminatingError( - new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateTypeConstrainedLanguage", ErrorCategory.PermissionDenied, null)); - } + 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 + && !CoreTypes.Contains(type)) + { + ThrowTerminatingError( + new ErrorRecord( + new PSNotSupportedException( + string.Format(NewObjectStrings.CannotCreateTypeLanguageMode, Context.LanguageMode.ToString())), + nameof(NewObjectStrings.CannotCreateTypeLanguageMode), + ErrorCategory.PermissionDenied, + targetObject: null)); + } + break; } // WinRT does not support creating instances of attribute & delegate WinRT types. @@ -207,7 +239,7 @@ protected override void BeginProcessing() ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes); if (ci != null && ci.IsPublic) { - _newObject = CallConstructor(type, new ConstructorInfo[] { ci }, new object[] { }); + _newObject = CallConstructor(type, new ConstructorInfo[] { ci }, Array.Empty()); if (_newObject != null && Property != null) { // 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 @@ -217,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). @@ -280,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); } } @@ -330,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) @@ -395,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; @@ -409,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); @@ -425,7 +464,7 @@ private void STAComCreateThreadProc(object createstruct) return; } - info.objectCreated = SafeCreateInstance(type, ArgumentList); + info.objectCreated = SafeCreateInstance(type); info.success = true; } catch (Exception e) @@ -437,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) { @@ -459,7 +503,7 @@ private object CreateComObject() { createInfo = new ComCreateInfo(); - Thread thread = new Thread(new ParameterizedThreadStart(STAComCreateThreadProc)); + Thread thread = new(new ParameterizedThreadStart(STAComCreateThreadProc)); thread.SetApartmentState(ApartmentState.STA); thread.Start(createInfo); @@ -498,12 +542,8 @@ private object CreateComObject() /// /// Native methods for dealing with COM objects. /// - internal class NewObjectNativeMethods + internal static class NewObjectNativeMethods { - private NewObjectNativeMethods() - { - } - /// Return Type: HRESULT->LONG->int [DllImport(PinvokeDllNames.CLSIDFromProgIDDllName)] internal static extern int CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] string lpszProgID, out Guid pclsid); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs index 3c95ebbc7de..8ae43a6e4fd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewAliasCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Internal; @@ -44,7 +43,7 @@ protected override void ProcessRecord() // Since the alias already exists, write an error. SessionStateException aliasExists = - new SessionStateException( + new( Name, SessionStateCategory.Alias, "AliasAlreadyExists", @@ -62,7 +61,7 @@ protected override void ProcessRecord() // Create the alias info AliasInfo newAlias = - new AliasInfo( + new( Name, Value, Context, 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..0e466f86d3f 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 = Empty.ToBool() ? Guid.Empty : Guid.NewGuid(); + } + + WriteObject(guid); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs index f8bb3d4e6ea..93b3eb209af 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.IO; using System.Management.Automation; @@ -41,7 +40,7 @@ protected override void EndProcessing() if (!string.IsNullOrEmpty(filePath)) { - FileInfo file = new FileInfo(filePath); + FileInfo file = new(filePath); WriteObject(file); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs index 0e633ca5b2e..a5d784da9bb 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTimeSpanCommand.cs @@ -68,25 +68,31 @@ public DateTime End /// Allows the user to override the day. /// [Parameter(ParameterSetName = "Time")] - public int Days { get; set; } = 0; + public int Days { get; set; } /// /// Allows the user to override the hour. /// [Parameter(ParameterSetName = "Time")] - public int Hours { get; set; } = 0; + public int Hours { get; set; } /// /// Allows the user to override the minute. /// [Parameter(ParameterSetName = "Time")] - public int Minutes { get; set; } = 0; + public int Minutes { get; set; } /// /// Allows the user to override the second. /// [Parameter(ParameterSetName = "Time")] - public int Seconds { get; set; } = 0; + public int Seconds { get; set; } + + /// + /// Allows the user to override the millisecond. + /// + [Parameter(ParameterSetName = "Time")] + public int Milliseconds { get; set; } #endregion @@ -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 fd69a2011fb..13b0234a02b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.Commands /// isExistingProperty is needed to distinguish whether a property exists and its value is null or /// the property does not exist at all. /// - internal class ObjectCommandPropertyValue + internal sealed class ObjectCommandPropertyValue { private ObjectCommandPropertyValue() { } @@ -30,7 +30,7 @@ internal ObjectCommandPropertyValue(object propVal) } /// - /// ObjectCommandPropertyValue constructor. + /// Initializes a new instance of the class. /// /// Property Value. /// Indicates if the Property value comparison has to be case sensitive or not. @@ -65,9 +65,9 @@ internal CultureInfo Culture } } - internal static readonly ObjectCommandPropertyValue NonExistingProperty = new ObjectCommandPropertyValue(); - internal static readonly ObjectCommandPropertyValue ExistingNullProperty = new ObjectCommandPropertyValue(null); - private bool _caseSensitive; + internal static readonly ObjectCommandPropertyValue NonExistingProperty = new(); + internal static readonly ObjectCommandPropertyValue ExistingNullProperty = new(null); + private readonly bool _caseSensitive; internal CultureInfo cultureInfo = null; /// @@ -77,8 +77,7 @@ internal CultureInfo Culture /// True if both the objects are same or else returns false. public override bool Equals(object inputObject) { - ObjectCommandPropertyValue objectCommandPropertyValueObject = inputObject as ObjectCommandPropertyValue; - if (objectCommandPropertyValueObject == null) + if (inputObject is not ObjectCommandPropertyValue objectCommandPropertyValueObject) { return false; } @@ -137,9 +136,10 @@ public override int GetHashCode() /// /// ObjectCommandComparer class. /// - internal class ObjectCommandComparer : IComparer + internal sealed class ObjectCommandComparer : IComparer { /// + /// Initializes a new instance of the class. /// Constructor that doesn't set any private field. /// Necessary because compareTo can compare two objects by calling /// ((ICompare)obj1).CompareTo(obj2) without using a key. @@ -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 @@ -210,26 +210,26 @@ public int Compare(object first, object second) second = secondMsh.BaseObject; } - if (LanguagePrimitives.TryCompare(first, second, !_caseSensitive, _cultureInfo, out int result)) + if (!LanguagePrimitives.TryCompare(first, second, !_caseSensitive, _cultureInfo, out int result)) { - return result * (_ascendingOrder ? 1 : -1); - } + // Note that this will occur if the objects do not support + // IComparable. We fall back to comparing as strings. - // Note that this will occur if the objects do not support - // IComparable. We fall back to comparing as strings. + // being here means the first object doesn't support ICompare + string firstString = PSObject.AsPSObject(first).ToString(); + string secondString = PSObject.AsPSObject(second).ToString(); - // being here means the first object doesn't support ICompare - string firstString = PSObject.AsPSObject(first).ToString(); - string secondString = PSObject.AsPSObject(second).ToString(); + result = _cultureInfo.CompareInfo.Compare(firstString, secondString, _caseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase); + } - return _cultureInfo.CompareInfo.Compare(firstString, secondString, _caseSensitive ? CompareOptions.None : CompareOptions.IgnoreCase) * (_ascendingOrder ? 1 : -1); + return _ascendingOrder ? result : -result; } - private CultureInfo _cultureInfo = null; + private readonly CultureInfo _cultureInfo = null; - private bool _ascendingOrder = true; + private readonly bool _ascendingOrder = true; - private bool _caseSensitive = false; + private readonly bool _caseSensitive = false; } #endregion } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs index c2ebc9261a1..596bbcaafc6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs @@ -24,7 +24,7 @@ internal static class SortObjectParameterDefinitionKeys /// /// - internal class SortObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class SortObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -36,7 +36,7 @@ protected override void SetEntries() /// /// - internal class GroupObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class GroupObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -57,7 +57,10 @@ public class ObjectCmdletBase : PSCmdlet [System.Diagnostics.CodeAnalysis.SuppressMessage("GoldMan", "#pw17903:UseOfLCID", Justification = "The CultureNumber is only used if the property has been set with a hex string starting with 0x")] public string Culture { - get { return _cultureInfo != null ? _cultureInfo.ToString() : null; } + get + { + return _cultureInfo?.ToString(); + } set { @@ -117,7 +120,7 @@ public abstract class ObjectBase : ObjectCmdletBase /// /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// Gets or Sets the Properties that would be used for Grouping, Sorting and Comparison. @@ -182,9 +185,9 @@ internal sealed class OrderByProperty /// /// A logical matrix where each row is an input object and its property values specified by Properties. /// - internal List OrderMatrix { get; } = null; + internal List OrderMatrix { get; } - internal OrderByPropertyComparer Comparer { get; } = null; + internal OrderByPropertyComparer Comparer { get; } internal List MshParameterList { @@ -211,7 +214,7 @@ private static void ProcessExpressionParameter( out List mshParameterList) { mshParameterList = null; - TerminatingErrorContext invocationContext = new TerminatingErrorContext(cmdlet); + TerminatingErrorContext invocationContext = new(cmdlet); // compare-object and group-object use the same definition here ParameterProcessor processor = cmdlet is SortObjectCommand ? new ParameterProcessor(new SortObjectExpressionParameterDefinition()) : @@ -235,7 +238,7 @@ internal void ProcessExpressionParameter( PSCmdlet cmdlet, object[] expr) { - TerminatingErrorContext invocationContext = new TerminatingErrorContext(cmdlet); + TerminatingErrorContext invocationContext = new(cmdlet); // compare-object and group-object use the same definition here ParameterProcessor processor = cmdlet is SortObjectCommand ? new ParameterProcessor(new SortObjectExpressionParameterDefinition()) : @@ -256,10 +259,7 @@ internal void ProcessExpressionParameter( } else { - if (_unExpandedParametersWithWildCardPattern == null) - { - _unExpandedParametersWithWildCardPattern = new List(); - } + _unExpandedParametersWithWildCardPattern ??= new List(); _unExpandedParametersWithWildCardPattern.Add(unexpandedParameter); } @@ -272,7 +272,7 @@ internal void ProcessExpressionParameter( // match property names on the incoming objects. private static List ExpandExpressions(List inputObjects, List unexpandedParameterList) { - List expandedParameterList = new List(); + List expandedParameterList = new(); if (unexpandedParameterList != null) { @@ -285,7 +285,7 @@ private static List ExpandExpressions(List inputObjects, } else { - SortedDictionary expandedPropertyNames = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + SortedDictionary expandedPropertyNames = new(StringComparer.OrdinalIgnoreCase); if (inputObjects != null) { foreach (object inputObject in inputObjects) @@ -304,7 +304,7 @@ private static List ExpandExpressions(List inputObjects, foreach (PSPropertyExpression expandedExpression in expandedPropertyNames.Values) { - MshParameter expandedParameter = new MshParameter(); + MshParameter expandedParameter = new(); expandedParameter.hash = (Hashtable)unexpandedParameter.hash.Clone(); expandedParameter.hash[FormatParameterDefinitionKeys.ExpressionEntryKey] = expandedExpression; @@ -327,7 +327,7 @@ private static void ExpandExpressions(PSObject inputObject, List U { PSPropertyExpression ex = (PSPropertyExpression)unexpandedParameter.GetEntry(FormatParameterDefinitionKeys.ExpressionEntryKey); - SortedDictionary expandedPropertyNames = new SortedDictionary(StringComparer.OrdinalIgnoreCase); + SortedDictionary expandedPropertyNames = new(StringComparer.OrdinalIgnoreCase); if (inputObject == null) { continue; @@ -340,7 +340,7 @@ private static void ExpandExpressions(PSObject inputObject, List U foreach (PSPropertyExpression expandedExpression in expandedPropertyNames.Values) { - MshParameter expandedParameter = new MshParameter(); + MshParameter expandedParameter = new(); expandedParameter.hash = (Hashtable)unexpandedParameter.hash.Clone(); expandedParameter.hash[FormatParameterDefinitionKeys.ExpressionEntryKey] = expandedExpression; @@ -358,9 +358,7 @@ internal static string[] GetDefaultKeyPropertySet(PSObject mshObj) return null; } - PSPropertySet defaultKeys = standardNames.Members["DefaultKeyPropertySet"] as PSPropertySet; - - if (defaultKeys == null) + if (standardNames.Members["DefaultKeyPropertySet"] is not PSPropertySet defaultKeys) { return null; } @@ -378,14 +376,14 @@ internal static List CreateOrderMatrix( List mshParameterList ) { - List orderMatrixToCreate = new List(); + List orderMatrixToCreate = new(); for (int index = 0; index < inputObjects.Count; index++) { PSObject so = inputObjects[index]; if (so == null || so == AutomationNull.Value) continue; - List evaluationErrors = new List(); - List propertyNotFoundMsgs = new List(); + List evaluationErrors = new(); + List propertyNotFoundMsgs = new(); OrderByPropertyEntry result = OrderByPropertyEntryEvaluationHelper.ProcessObject(so, mshParameterList, evaluationErrors, propertyNotFoundMsgs, originalIndex: index); foreach (ErrorRecord err in evaluationErrors) @@ -480,7 +478,7 @@ bool caseSensitive } /// - /// OrderByProperty constructor. + /// Initializes a new instance of the class. /// internal OrderByProperty() { @@ -509,8 +507,8 @@ internal OrderByPropertyEntry CreateOrderByPropertyEntry( ExpandExpressions(inputObject, _unExpandedParametersWithWildCardPattern, _mshParameterList); } - List evaluationErrors = new List(); - List propertyNotFoundMsgs = new List(); + List evaluationErrors = new(); + List propertyNotFoundMsgs = new(); OrderByPropertyEntry result = OrderByPropertyEntryEvaluationHelper.ProcessObject(inputObject, _mshParameterList, evaluationErrors, propertyNotFoundMsgs, isCaseSensitive, cultureInfo); foreach (ErrorRecord err in evaluationErrors) @@ -529,7 +527,7 @@ internal OrderByPropertyEntry CreateOrderByPropertyEntry( #endregion Utils // list of processed parameters obtained from the Expression array - private List _mshParameterList = null; + private readonly List _mshParameterList = null; // list of unprocessed parameters obtained from the Expression array. private List _unexpandedParameterList = null; @@ -545,7 +543,7 @@ internal static OrderByPropertyEntry ProcessObject(PSObject inputObject, List orderValues = new List(); + internal List orderValues = new(); // The originalIndex field was added to enable stable heap-sorts (Top N/Bottom N) internal int originalIndex = -1; @@ -632,7 +630,7 @@ internal sealed class OrderByPropertyEntry internal bool comparable = false; } - internal class OrderByPropertyComparer : IComparer + internal sealed class OrderByPropertyComparer : IComparer { internal OrderByPropertyComparer(bool[] ascending, CultureInfo cultureInfo, bool caseSensitive) { @@ -698,10 +696,10 @@ internal static OrderByPropertyComparer CreateComparer(List + internal sealed class IndexedOrderByPropertyComparer : IComparer { internal IndexedOrderByPropertyComparer(OrderByPropertyComparer orderByPropertyComparer) { @@ -727,6 +725,6 @@ public int Compare(OrderByPropertyEntry lhs, OrderByPropertyEntry rhs) return result; } - private OrderByPropertyComparer _orderByPropertyComparer = null; + private readonly OrderByPropertyComparer _orderByPropertyComparer = null; } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs new file mode 100644 index 00000000000..d3dfd30f9e5 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointAccessorCommandBase.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for Get/Set-PSBreakpoint. + /// + public abstract class PSBreakpointAccessorCommandBase : PSBreakpointCommandBase + { + #region strings + + internal const string CommandParameterSetName = "Command"; + internal const string LineParameterSetName = "Line"; + internal const string VariableParameterSetName = "Variable"; + + #endregion strings + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs new file mode 100644 index 00000000000..61d7236977d --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCommandBase.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for PSBreakpoint cmdlets. + /// + public abstract class PSBreakpointCommandBase : PSCmdlet + { + #region parameters + + /// + /// Gets or sets the runspace where the breakpoints will be used. + /// + [Parameter] + [ValidateNotNull] + [Runspace] + public virtual Runspace Runspace { get; set; } + + #endregion parameters + + #region overrides + + /// + /// Identifies the default runspace. + /// + protected override void BeginProcessing() + { + Runspace ??= Context.CurrentRunspace; + } + + #endregion overrides + + #region protected methods + + /// + /// Write the given breakpoint out to the pipeline, decorated with the runspace instance id if appropriate. + /// + /// The breakpoint to write to the pipeline. + protected virtual void ProcessBreakpoint(Breakpoint breakpoint) + { + if (Runspace != Context.CurrentRunspace) + { + var pso = new PSObject(breakpoint); + pso.Properties.Add(new PSNoteProperty(RemotingConstants.RunspaceIdNoteProperty, Runspace.InstanceId)); + WriteObject(pso); + } + else + { + WriteObject(breakpoint); + } + } + + #endregion protected methods + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs deleted file mode 100644 index cb0e9ea2435..00000000000 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointCreationBase.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.Collections.ObjectModel; -using System.IO; -using System.Management.Automation; -using System.Management.Automation.Internal; - -namespace Microsoft.PowerShell.Commands -{ - /// - /// Base class for Set/New-PSBreakpoint. - /// - public class PSBreakpointCreationBase : PSCmdlet - { - internal const string CommandParameterSetName = "Command"; - internal const string LineParameterSetName = "Line"; - internal const string VariableParameterSetName = "Variable"; - - #region parameters - - /// - /// The action to take when hitting this breakpoint. - /// - [Parameter(ParameterSetName = CommandParameterSetName)] - [Parameter(ParameterSetName = LineParameterSetName)] - [Parameter(ParameterSetName = VariableParameterSetName)] - public ScriptBlock Action { get; set; } - - /// - /// The column to set the breakpoint on. - /// - [Parameter(Position = 2, ParameterSetName = LineParameterSetName)] - [ValidateRange(1, int.MaxValue)] - public int Column { get; set; } - - /// - /// The command(s) to set the breakpoint on. - /// - [Alias("C")] - [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)] - public string[] Command { get; set; } - - /// - /// The line to set the breakpoint on. - /// - [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)] - public int[] Line { get; set; } - - /// - /// The script to set the breakpoint on. - /// - [Parameter(ParameterSetName = CommandParameterSetName, Position = 0)] - [Parameter(ParameterSetName = LineParameterSetName, Mandatory = true, Position = 0)] - [Parameter(ParameterSetName = VariableParameterSetName, Position = 0)] - [ValidateNotNull] - public string[] Script { get; set; } - - /// - /// The variables to set the breakpoint(s) on. - /// - [Alias("V")] - [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)] - public string[] Variable { get; set; } - - /// - /// The access type for variable breakpoints to break on. - /// - [Parameter(ParameterSetName = VariableParameterSetName)] - public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; - - #endregion parameters - - internal Collection ResolveScriptPaths() - { - Collection scripts = new Collection(); - - if (Script != null) - { - foreach (string script in Script) - { - Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); - - for (int i = 0; i < scriptPaths.Count; i++) - { - string providerPath = scriptPaths[i].ProviderPath; - - if (!File.Exists(providerPath)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), - "NewPSBreakpoint:FileDoesNotExist", - ErrorCategory.InvalidArgument, - null)); - - continue; - } - - string extension = Path.GetExtension(providerPath); - - if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) - { - WriteError( - new ErrorRecord( - new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), - "NewPSBreakpoint:WrongExtension", - ErrorCategory.InvalidArgument, - null)); - continue; - } - - scripts.Add(Path.GetFullPath(providerPath)); - } - } - } - - return scripts; - } - } -} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointUpdaterCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointUpdaterCommandBase.cs new file mode 100644 index 00000000000..f186fbe1baa --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/PSBreakpointUpdaterCommandBase.cs @@ -0,0 +1,168 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Management.Automation; +using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; + +namespace Microsoft.PowerShell.Commands +{ + /// + /// Base class for Enable/Disable/Remove-PSBreakpoint. + /// + public abstract class PSBreakpointUpdaterCommandBase : PSBreakpointCommandBase + { + #region strings + + internal const string BreakpointParameterSetName = "Breakpoint"; + internal const string IdParameterSetName = "Id"; + + #endregion strings + + #region parameters + + /// + /// Gets or sets the breakpoint to enable. + /// + [Parameter(ParameterSetName = BreakpointParameterSetName, ValueFromPipeline = true, Position = 0, Mandatory = true)] + [ValidateNotNull] + public Breakpoint[] Breakpoint { get; set; } + + /// + /// Gets or sets the Id of the breakpoint to enable. + /// + [Parameter(ParameterSetName = IdParameterSetName, ValueFromPipelineByPropertyName = true, Position = 0, Mandatory = true)] + [ValidateNotNull] + public int[] Id { get; set; } + + /// + /// Gets or sets the runspace where the breakpoints will be used. + /// + [Parameter(ParameterSetName = IdParameterSetName, ValueFromPipelineByPropertyName = true)] + [Alias("RunspaceId")] + [Runspace] + public override Runspace Runspace { get; set; } + + #endregion parameters + + #region overrides + + /// + /// Gathers the list of breakpoints to process and calls ProcessBreakpoints. + /// + protected override void ProcessRecord() + { + if (ParameterSetName.Equals(BreakpointParameterSetName, StringComparison.OrdinalIgnoreCase)) + { + foreach (Breakpoint breakpoint in Breakpoint) + { + if (ShouldProcessInternal(breakpoint.ToString()) && + TryGetRunspace(breakpoint)) + { + ProcessBreakpoint(breakpoint); + } + } + } + else + { + Debug.Assert( + ParameterSetName.Equals(IdParameterSetName, StringComparison.OrdinalIgnoreCase), + $"There should be no other parameter sets besides '{BreakpointParameterSetName}' and '{IdParameterSetName}'."); + + foreach (int id in Id) + { + Breakpoint breakpoint; + if (TryGetBreakpoint(id, out breakpoint) && + ShouldProcessInternal(breakpoint.ToString())) + { + ProcessBreakpoint(breakpoint); + } + } + } + } + + #endregion overrides + + #region private data + + private readonly Dictionary runspaces = new(); + + #endregion private data + + #region private methods + + private bool TryGetRunspace(Breakpoint breakpoint) + { + // Breakpoints retrieved from another runspace will have a RunspaceId note property of type Guid on them. + var pso = new PSObject(breakpoint); + var runspaceInstanceIdProperty = pso.Properties[RemotingConstants.RunspaceIdNoteProperty]; + if (runspaceInstanceIdProperty == null) + { + Runspace = Context.CurrentRunspace; + return true; + } + + Debug.Assert(runspaceInstanceIdProperty.TypeNameOfValue.Equals("System.Guid", StringComparison.OrdinalIgnoreCase), "Instance ids must be GUIDs."); + + var runspaceInstanceId = (Guid)runspaceInstanceIdProperty.Value; + if (runspaces.ContainsKey(runspaceInstanceId)) + { + Runspace = runspaces[runspaceInstanceId]; + return true; + } + + var matchingRunspaces = GetRunspaceUtils.GetRunspacesByInstanceId(new[] { runspaceInstanceId }); + if (matchingRunspaces.Count != 1) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.RunspaceInstanceIdNotFound, runspaceInstanceId)), + "PSBreakpoint:RunspaceInstanceIdNotFound", + ErrorCategory.InvalidArgument, + null)); + return false; + } + + Runspace = runspaces[runspaceInstanceId] = matchingRunspaces[0]; + return true; + } + + private bool TryGetBreakpoint(int id, out Breakpoint breakpoint) + { + breakpoint = Runspace.Debugger.GetBreakpoint(id); + + if (breakpoint == null) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.BreakpointIdNotFound, id)), + "PSBreakpoint:BreakpointIdNotFound", + ErrorCategory.InvalidArgument, + null)); + return false; + } + + return true; + } + + private bool ShouldProcessInternal(string target) + { + // ShouldProcess should be called only if the WhatIf or Confirm parameters are passed in explicitly. + // It should *not* be called if we are in a nested debug prompt and the current running command was + // run with -WhatIf or -Confirm, because this prevents the user from adding/removing breakpoints inside + // a debugger stop. + if (MyInvocation.BoundParameters.ContainsKey("WhatIf") || + MyInvocation.BoundParameters.ContainsKey("Confirm")) + { + return ShouldProcess(target); + } + + return true; + } + + #endregion private methods + } +} diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs index 23b4bac873a..3fabe75de24 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ReadConsoleCmdlet.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -16,15 +15,13 @@ namespace Microsoft.PowerShell.Commands /// /// Retrieves input from the host virtual console and writes it to the pipeline output. /// - [Cmdlet(VerbsCommunications.Read, "Host", DefaultParameterSetName = "AsString", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096610")] [OutputType(typeof(string), typeof(SecureString))] public sealed class ReadHostCommand : PSCmdlet { /// - /// Constructs a new instance. + /// Initializes a new instance of the class. /// - public ReadHostCommand() { @@ -36,7 +33,6 @@ public sealed class ReadHostCommand : PSCmdlet /// /// The objects to display on the host before collecting input. /// - [Parameter(Position = 0, ValueFromRemainingArguments = true)] [AllowNull] public @@ -55,7 +51,7 @@ public sealed class ReadHostCommand : PSCmdlet } /// - /// Gets or sets to no echo the input as is is typed. If set then the cmdlet returns a secure string. + /// Gets or sets to no echo the input as is typed. If set then the cmdlet returns a secure string. /// [Parameter(ParameterSetName = "AsSecureString")] public @@ -74,7 +70,7 @@ public sealed class ReadHostCommand : PSCmdlet } /// - /// Gets or sets whether the console will echo the input as is is typed. If set then the cmdlet returns a regular string. + /// Gets or sets whether the console will echo the input as is typed. If set then the cmdlet returns a regular string. /// [Parameter(ParameterSetName = "AsString")] public @@ -101,7 +97,7 @@ protected override void BeginProcessing() IEnumerator e = LanguagePrimitives.GetEnumerator(_prompt); if (e != null) { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); while (e.MoveNext()) { @@ -129,17 +125,17 @@ protected override void BeginProcessing() promptString = (string)LanguagePrimitives.ConvertTo(_prompt, typeof(string), CultureInfo.InvariantCulture); } - FieldDescription fd = new FieldDescription(promptString); - if (AsSecureString) + FieldDescription fd = new(promptString); + if (AsSecureString || MaskInput) { - fd.SetParameterType(typeof(System.Security.SecureString)); + fd.SetParameterType(typeof(SecureString)); } else { fd.SetParameterType(typeof(string)); } - Collection fdc = new Collection(); + Collection fdc = new(); fdc.Add(fd); Dictionary result = Host.UI.Prompt(string.Empty, string.Empty, fdc); @@ -149,27 +145,37 @@ protected override void BeginProcessing() { foreach (PSObject o in result.Values) { - WriteObject(o); + if (MaskInput && o?.BaseObject is SecureString secureString) + { + WriteObject(Utils.GetStringFromSecureString(secureString)); + } + else + { + WriteObject(o); + } } } } else { object result; - if (AsSecureString) + if (AsSecureString || MaskInput) { result = Host.UI.ReadLineAsSecureString(); } - else if (MaskInput) - { - result = Host.UI.ReadLineMaskedAsString(); - } else { result = Host.UI.ReadLine(); } - WriteObject(result); + if (MaskInput) + { + WriteObject(Utils.GetStringFromSecureString((SecureString)result)); + } + else + { + WriteObject(result); + } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs index ce72dac51fe..d4e7dc784c1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterObjectEventCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; namespace Microsoft.PowerShell.Commands diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs index f04124e9a4c..6e4ced90760 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RegisterPSEventCommand.cs @@ -42,7 +42,7 @@ protected override object GetSourceObject() (!(bool)Forward) ) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException(EventingStrings.ActionMandatoryForLocal), "ACTION_MANDATORY_FOR_LOCAL", ErrorCategory.InvalidArgument, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs index 458b471b322..de324b33fb8 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Remove-PSBreakpoint.cs @@ -8,16 +8,20 @@ namespace Microsoft.PowerShell.Commands /// /// This class implements Remove-PSBreakpoint. /// - [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = "Breakpoint", + [Cmdlet(VerbsCommon.Remove, "PSBreakpoint", SupportsShouldProcess = true, DefaultParameterSetName = BreakpointParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2097134")] - public class RemovePSBreakpointCommand : PSBreakpointCommandBase + public class RemovePSBreakpointCommand : PSBreakpointUpdaterCommandBase { + #region overrides + /// /// Removes the given breakpoint. /// protected override void ProcessBreakpoint(Breakpoint breakpoint) { - this.Context.Debugger.RemoveBreakpoint(breakpoint); + Runspace.Debugger.RemoveBreakpoint(breakpoint); } + + #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 7b44a0c2664..15c48efd847 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveAliasCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Internal; @@ -10,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 { @@ -26,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; } /// @@ -62,8 +62,8 @@ protected override void ProcessRecord() } else { - ItemNotFoundException notAliasFound = new ItemNotFoundException(StringUtil.Format(AliasCommandStrings.NoAliasFound, "name", aliasName)); - ErrorRecord error = new ErrorRecord(notAliasFound, "ItemNotFoundException", ErrorCategory.ObjectNotFound, aliasName); + ItemNotFoundException notAliasFound = new(StringUtil.Format(AliasCommandStrings.NoAliasFound, "name", aliasName)); + ErrorRecord error = new(notAliasFound, "ItemNotFoundException", ErrorCategory.ObjectNotFound, aliasName); WriteError(error); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs index 14acbbdf6af..4b8ade4a94a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RemoveEventCommand.cs @@ -111,7 +111,7 @@ protected override void ProcessRecord() (!WildcardPattern.ContainsWildcardCharacters(_sourceIdentifier)) && (!foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( string.Format( System.Globalization.CultureInfo.CurrentCulture, @@ -124,7 +124,7 @@ protected override void ProcessRecord() } else if ((_eventIdentifier >= 0) && (!foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( string.Format( System.Globalization.CultureInfo.CurrentCulture, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs new file mode 100644 index 00000000000..696813449cb --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/RunspaceAttribute.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma warning disable 1634, 1691 +#pragma warning disable 56506 + +using Microsoft.PowerShell.Commands; + +namespace System.Management.Automation.Runspaces +{ + /// + /// Defines the attribute used to designate a cmdlet parameter as one that + /// should accept runspaces. + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + public sealed class RunspaceAttribute : ArgumentTransformationAttribute + { + /// + /// Transforms the input data to a Runspace. + /// + /// + /// The engine APIs for the context under which the transformation is being + /// made. + /// + /// + /// If a string, the transformation uses the input as the runspace name. + /// If an int, the transformation uses the input as the runspace ID. + /// If a guid, the transformation uses the input as the runspace GUID. + /// If already a Runspace, the transform does nothing. + /// + /// A runspace object representing the inputData. + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + if (engineIntrinsics?.Host?.UI == null) + { + throw PSTraceSource.NewArgumentNullException("engineIntrinsics"); + } + + if (inputData == null) + { + return null; + } + + // Try to coerce the input as a runspace + Runspace runspace = LanguagePrimitives.FromObjectAs(inputData); + if (runspace != null) + { + return runspace; + } + + // Try to coerce the runspace if the user provided a string, int, or guid + switch (inputData) + { + case string name: + var runspacesByName = GetRunspaceUtils.GetRunspacesByName(new[] { name }); + if (runspacesByName.Count == 1) + { + return runspacesByName[0]; + } + + break; + + case int id: + var runspacesById = GetRunspaceUtils.GetRunspacesById(new[] { id }); + if (runspacesById.Count == 1) + { + return runspacesById[0]; + } + + break; + + case Guid guid: + var runspacesByGuid = GetRunspaceUtils.GetRunspacesByInstanceId(new[] { guid }); + if (runspacesByGuid.Count == 1) + { + return runspacesByGuid[0]; + } + + break; + + default: + // Non-convertible type + break; + } + + // If we couldn't get a single runspace, return the inputData + return inputData; + } + + /// + /// Gets a flag indicating whether or not null optional parameters are transformed. + /// + public override bool TransformNullOptionalParameters { get { return false; } } + } +} + +#pragma warning restore 56506 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 bba2ac2873d..e0b0bff07c5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs @@ -12,50 +12,7 @@ namespace Microsoft.PowerShell.Commands { - /// - /// Helper class to do wildcard matching on PSPropertyExpressions. - /// - internal sealed class PSPropertyExpressionFilter - { - /// - /// Construct the class, using an array of patterns. - /// - /// Array of pattern strings to use. - internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) - { - if (wildcardPatternsStrings == null) - { - throw new ArgumentNullException(nameof(wildcardPatternsStrings)); - } - - _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; - for (int k = 0; k < wildcardPatternsStrings.Length; k++) - { - _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); - } - } - - /// - /// Try to match the expression against the array of wildcard patterns. - /// The first match shortcircuits the search. - /// - /// PSPropertyExpression to test against. - /// True if there is a match, else false. - internal bool IsMatch(PSPropertyExpression expression) - { - for (int k = 0; k < _wildcardPatterns.Length; k++) - { - if (_wildcardPatterns[k].IsMatch(expression.ToString())) - return true; - } - - return false; - } - - private WildcardPattern[] _wildcardPatterns; - } - - internal class SelectObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -76,7 +33,7 @@ public sealed class SelectObjectCommand : PSCmdlet /// /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// @@ -90,14 +47,14 @@ public sealed class SelectObjectCommand : PSCmdlet /// [Parameter(ParameterSetName = "DefaultParameter")] [Parameter(ParameterSetName = "SkipLastParameter")] - public string[] ExcludeProperty { get; set; } = null; + public string[] ExcludeProperty { get; set; } /// /// /// [Parameter(ParameterSetName = "DefaultParameter")] [Parameter(ParameterSetName = "SkipLastParameter")] - public string ExpandProperty { get; set; } = null; + public string ExpandProperty { get; set; } /// /// @@ -112,6 +69,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; } + /// /// /// @@ -146,19 +110,20 @@ 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; } = 0; + public int Skip { get; set; } /// /// Skip the specified number of items from end. /// [Parameter(ParameterSetName = "SkipLastParameter")] [ValidateRange(0, int.MaxValue)] - public int SkipLast { get; set; } = 0; + public int SkipLast { get; set; } /// /// With this switch present, the cmdlet won't "short-circuit" @@ -173,7 +138,7 @@ public int First /// /// [Parameter(ParameterSetName = "IndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Index { @@ -196,7 +161,7 @@ public int[] Index /// /// [Parameter(ParameterSetName = "SkipIndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] SkipIndex { @@ -222,7 +187,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) { @@ -304,8 +269,11 @@ public PSObject StreamingDequeue() } private int _streamedObjectCount; - private int _first, _last, _skip, _skipLast; - private bool _firstOrLastSpecified; + private readonly int _first; + private readonly int _last; + private int _skip; + private readonly int _skipLast; + private readonly bool _firstOrLastSpecified; } /// @@ -320,7 +288,7 @@ public PSObject StreamingDequeue() private PSPropertyExpressionFilter _exclusionFilter; - private class UniquePSObjectHelper + private sealed class UniquePSObjectHelper { internal UniquePSObjectHelper(PSObject o, int notePropertyCount) { @@ -337,9 +305,9 @@ internal UniquePSObjectHelper(PSObject o, int notePropertyCount) private void ProcessExpressionParameter() { - TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); + TerminatingErrorContext invocationContext = new(this); ParameterProcessor processor = - new ParameterProcessor(new SelectObjectExpressionParameterDefinition()); + new(new SelectObjectExpressionParameterDefinition()); if ((Property != null) && (Property.Length != 0)) { // Build property list taking into account the wildcards and @{name=;expression=} @@ -378,7 +346,7 @@ private void ProcessObject(PSObject inputObject) } // If property parameter is mentioned - List matchedProperties = new List(); + List matchedProperties = new(); foreach (MshParameter p in _propertyMshParameterList) { ProcessParameter(p, inputObject, matchedProperties); @@ -386,10 +354,10 @@ private void ProcessObject(PSObject inputObject) if (string.IsNullOrEmpty(ExpandProperty)) { - PSObject result = new PSObject(); + PSObject result = new(); if (matchedProperties.Count != 0) { - HashSet propertyNames = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet propertyNames = new(StringComparer.OrdinalIgnoreCase); foreach (PSNoteProperty noteProperty in matchedProperties) { @@ -427,13 +395,17 @@ private void ProcessParameter(MshParameter p, PSObject inputObject, List expressionResults = new List(); + List expressionResults = new(); foreach (PSPropertyExpression resolvedName in ex.ResolveNames(inputObject)) { if (_exclusionFilter == null || !_exclusionFilter.IsMatch(resolvedName)) { List tempExprResults = resolvedName.GetValues(inputObject); - if (tempExprResults == null) continue; + if (tempExprResults == null) + { + continue; + } + foreach (PSPropertyExpressionResult mshExpRes in tempExprResults) { expressionResults.Add(mshExpRes); @@ -452,7 +424,7 @@ private void ProcessParameter(MshParameter p, PSObject inputObject, List 1) { string errorMsg = SelectObjectStrings.RenamingMultipleResults; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(errorMsg), "RenamingMultipleResults", ErrorCategory.InvalidOperation, @@ -502,7 +474,7 @@ private void ProcessExpandParameter(MshParameter p, PSObject inputObject, if (expressionResults.Count == 0) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.PropertyNotFound, ExpandProperty), "ExpandPropertyNotFound", ErrorCategory.InvalidArgument, @@ -512,7 +484,7 @@ private void ProcessExpandParameter(MshParameter p, PSObject inputObject, if (expressionResults.Count > 1) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewArgumentException("ExpandProperty", SelectObjectStrings.MutlipleExpandProperties, ExpandProperty), "MutlipleExpandProperties", ErrorCategory.InvalidArgument, @@ -524,7 +496,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) @@ -544,7 +519,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 @@ -559,7 +537,7 @@ private void ProcessExpandParameter(MshParameter p, PSObject inputObject, } else { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( r.Exception, "PropertyEvaluationExpand", ErrorCategory.InvalidResult, @@ -592,7 +570,7 @@ private void AddNoteProperties(PSObject expandedObject, PSObject inputObject, IE private void WriteAlreadyExistingPropertyError(string name, object inputObject, string errorId) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewArgumentException("Property", SelectObjectStrings.AlreadyExistingProperty, name), errorId, ErrorCategory.InvalidOperation, @@ -620,7 +598,11 @@ private void FilteredWriteObject(PSObject obj, List addedNotePro bool isObjUnique = true; foreach (UniquePSObjectHelper uniqueObj in _uniques) { - ObjectCommandComparer comparer = new ObjectCommandComparer(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)) { @@ -843,7 +825,7 @@ protected override void EndProcessing() [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "This exception is internal and never thrown by any public API")] [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception is internal and never thrown by any public API")] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception is internal and never thrown by any public API")] - internal class SelectObjectException : SystemException + internal sealed class SelectObjectException : SystemException { internal ErrorRecord ErrorRecord { get; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs index 78e65d806a3..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,9 +61,23 @@ public sealed class SendMailMessage : PSCmdlet [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("BE")] [ValidateNotNullOrEmpty] - [ArgumentEncodingCompletionsAttribute] - [ArgumentToEncodingTransformationAttribute] - public Encoding Encoding { get; set; } = Encoding.ASCII; + [ArgumentEncodingCompletions] + [ArgumentToEncodingTransformation] + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.ASCII; /// /// Gets or sets the address collection that contains the @@ -151,7 +165,7 @@ public sealed class SendMailMessage : PSCmdlet /// Value must be greater than zero. /// [Parameter(ValueFromPipelineByPropertyName = true)] - [ValidateRange(0, Int32.MaxValue)] + [ValidateRange(0, int.MaxValue)] public int Port { get; set; } #endregion @@ -159,7 +173,7 @@ public sealed class SendMailMessage : PSCmdlet #region Private variables and methods // Instantiate a new instance of MailMessage - private MailMessage _mMailMessage = new MailMessage(); + private readonly MailMessage _mMailMessage = new(); private SmtpClient _mSmtpClient = null; @@ -202,7 +216,7 @@ private void AddAddressesToMailMessage(object address, string param) } catch (FormatException e) { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, null); + ErrorRecord er = new(e, "FormatException", ErrorCategory.InvalidType, null); WriteError(er); continue; } @@ -225,7 +239,7 @@ protected override void BeginProcessing() } catch (FormatException e) { - ErrorRecord er = new ErrorRecord(e, "FormatException", ErrorCategory.InvalidType, From); + ErrorRecord er = new(e, "FormatException", ErrorCategory.InvalidType, From); ThrowTerminatingError(er); } @@ -280,7 +294,7 @@ protected override void BeginProcessing() if (string.IsNullOrEmpty(SmtpServer)) { - ErrorRecord er = new ErrorRecord(new InvalidOperationException(SendMailMessageStrings.HostNameValue), null, ErrorCategory.InvalidArgument, null); + ErrorRecord er = new(new InvalidOperationException(SendMailMessageStrings.HostNameValue), null, ErrorCategory.InvalidArgument, null); this.ThrowTerminatingError(er); } @@ -330,7 +344,7 @@ protected override void ProcessRecord() PathUtils.ReportFileOpenFailure(this, filepath, e); } - Attachment mailAttachment = new Attachment(filepath); + Attachment mailAttachment = new(filepath); _mMailMessage.Attachments.Add(mailAttachment); } } @@ -348,30 +362,30 @@ protected override void EndProcessing() } catch (SmtpFailedRecipientsException ex) { - ErrorRecord er = new ErrorRecord(ex, "SmtpFailedRecipientsException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new(ex, "SmtpFailedRecipientsException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } catch (SmtpException ex) { if (ex.InnerException != null) { - ErrorRecord er = new ErrorRecord(new SmtpException(ex.InnerException.Message), "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new(new SmtpException(ex.InnerException.Message), "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } else { - ErrorRecord er = new ErrorRecord(ex, "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new(ex, "SmtpException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } } catch (InvalidOperationException ex) { - ErrorRecord er = new ErrorRecord(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new(ex, "InvalidOperationException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } catch (System.Security.Authentication.AuthenticationException ex) { - ErrorRecord er = new ErrorRecord(ex, "AuthenticationException", ErrorCategory.InvalidOperation, _mSmtpClient); + ErrorRecord er = new(ex, "AuthenticationException", ErrorCategory.InvalidOperation, _mSmtpClient); WriteError(er); } finally diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs index cb4b14a6619..ccf17652c79 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Set-PSBreakpoint.cs @@ -7,6 +7,7 @@ using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; +using System.Management.Automation.Runspaces; namespace Microsoft.PowerShell.Commands { @@ -14,14 +15,75 @@ namespace Microsoft.PowerShell.Commands /// This class implements Set-PSBreakpoint command. /// [Cmdlet(VerbsCommon.Set, "PSBreakpoint", DefaultParameterSetName = LineParameterSetName, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096623")] - [OutputType(typeof(VariableBreakpoint), typeof(CommandBreakpoint), typeof(LineBreakpoint))] - public class SetPSBreakpointCommand : PSBreakpointCreationBase + [OutputType(typeof(CommandBreakpoint), ParameterSetName = new string[] { CommandParameterSetName })] + [OutputType(typeof(LineBreakpoint), ParameterSetName = new string[] { LineParameterSetName })] + [OutputType(typeof(VariableBreakpoint), ParameterSetName = new string[] { VariableParameterSetName })] + public class SetPSBreakpointCommand : PSBreakpointAccessorCommandBase { + #region parameters + + /// + /// Gets or sets the action to take when hitting this breakpoint. + /// + [Parameter(ParameterSetName = CommandParameterSetName)] + [Parameter(ParameterSetName = LineParameterSetName)] + [Parameter(ParameterSetName = VariableParameterSetName)] + public ScriptBlock Action { get; set; } + + /// + /// Gets or sets the column to set the breakpoint on. + /// + [Parameter(Position = 2, ParameterSetName = LineParameterSetName)] + [ValidateRange(1, int.MaxValue)] + public int Column { get; set; } + + /// + /// Gets or sets the command(s) to set the breakpoint on. + /// + [Alias("C")] + [Parameter(ParameterSetName = CommandParameterSetName, Mandatory = true)] + public string[] Command { get; set; } + + /// + /// Gets or sets the line to set the breakpoint on. + /// + [Parameter(Position = 1, ParameterSetName = LineParameterSetName, Mandatory = true)] + public int[] Line { get; set; } + + /// + /// Gets or sets the script to set the breakpoint on. + /// + [Parameter(ParameterSetName = CommandParameterSetName, Position = 0)] + [Parameter(ParameterSetName = LineParameterSetName, Mandatory = true, Position = 0)] + [Parameter(ParameterSetName = VariableParameterSetName, Position = 0)] + [ValidateNotNull] + public string[] Script { get; set; } + + /// + /// Gets or sets the variables to set the breakpoint(s) on. + /// + [Alias("V")] + [Parameter(ParameterSetName = VariableParameterSetName, Mandatory = true)] + public string[] Variable { get; set; } + + /// + /// Gets or sets the access type for variable breakpoints to break on. + /// + [Parameter(ParameterSetName = VariableParameterSetName)] + public VariableAccessMode Mode { get; set; } = VariableAccessMode.Write; + + #endregion parameters + + #region overrides + /// /// Verifies that debugging is supported. /// protected override void BeginProcessing() { + // Call the base method to ensure Runspace is initialized properly. + base.BeginProcessing(); + // Check whether we are executing on a remote session and if so // whether the RemoteScript debug option is selected. if (this.Context.InternalHost.ExternalHost is System.Management.Automation.Remoting.ServerRemoteHost && @@ -61,11 +123,49 @@ protected override void BeginProcessing() protected override void ProcessRecord() { // If there is a script, resolve its path - Collection scripts = ResolveScriptPaths(); + Collection scripts = new(); + + if (Script != null) + { + foreach (string script in Script) + { + Collection scriptPaths = SessionState.Path.GetResolvedPSPathFromPSPath(script); + + for (int i = 0; i < scriptPaths.Count; i++) + { + string providerPath = scriptPaths[i].ProviderPath; + + if (!File.Exists(providerPath)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.FileDoesNotExist, providerPath)), + "NewPSBreakpoint:FileDoesNotExist", + ErrorCategory.InvalidArgument, + null)); + + continue; + } + + string extension = Path.GetExtension(providerPath); + + if (!extension.Equals(".ps1", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".psm1", StringComparison.OrdinalIgnoreCase)) + { + WriteError( + new ErrorRecord( + new ArgumentException(StringUtil.Format(Debugger.WrongExtension, providerPath)), + "NewPSBreakpoint:WrongExtension", + ErrorCategory.InvalidArgument, + null)); + continue; + } + + scripts.Add(Path.GetFullPath(providerPath)); + } + } + } - // // If it is a command breakpoint... - // if (ParameterSetName.Equals(CommandParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Command.Length; i++) @@ -74,20 +174,18 @@ protected override void ProcessRecord() { foreach (string path in scripts) { - WriteObject( - Context.Debugger.SetCommandBreakpoint(Command[i], Action, path)); + ProcessBreakpoint( + Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path)); } } else { - WriteObject( - Context.Debugger.SetCommandBreakpoint(Command[i], Action, path: null)); + ProcessBreakpoint( + Runspace.Debugger.SetCommandBreakpoint(Command[i], Action, path: null)); } } } - // // If it is a variable breakpoint... - // else if (ParameterSetName.Equals(VariableParameterSetName, StringComparison.OrdinalIgnoreCase)) { for (int i = 0; i < Variable.Length; i++) @@ -96,20 +194,18 @@ protected override void ProcessRecord() { foreach (string path in scripts) { - WriteObject( - Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path)); + ProcessBreakpoint( + Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path)); } } else { - WriteObject( - Context.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path: null)); + ProcessBreakpoint( + Runspace.Debugger.SetVariableBreakpoint(Variable[i], Mode, Action, path: null)); } } } - // // Else it is the default parameter set (Line breakpoint)... - // else { Debug.Assert(ParameterSetName.Equals(LineParameterSetName, StringComparison.OrdinalIgnoreCase)); @@ -130,11 +226,13 @@ protected override void ProcessRecord() foreach (string path in scripts) { - WriteObject( - Context.Debugger.SetLineBreakpoint(path, Line[i], Column, Action)); + ProcessBreakpoint( + Runspace.Debugger.SetLineBreakpoint(path, Line[i], Column, Action)); } } } } + + #endregion overrides } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs index b2746625375..6c0007fa2a2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetAliasCommand.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; using System.Management.Automation.Internal; @@ -24,7 +23,7 @@ protected override void ProcessRecord() // Create the alias info AliasInfo aliasToSet = - new AliasInfo( + new( Name, Value, Context, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/SetDateCommand.cs index e00a3bda2d9..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,42 +70,59 @@ 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 NativeMethods.SystemTime(); - 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; + NativeMethods.SystemTime systemTime = new(); + 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 } // output DateTime object wrapped in an PSObject with DisplayHint attached - PSObject outputObj = new PSObject(dateToUse); - PSNoteProperty note = new PSNoteProperty("DisplayHint", DisplayHint); + PSObject outputObj = new(dateToUse); + 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 701508ed4ff..60b05807d48 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommand.cs @@ -50,7 +50,7 @@ public class ShowCommandCommand : PSCmdlet, IDisposable /// /// Record the EndProcessing error. /// - private PSDataCollection _errors = new PSDataCollection(); + private PSDataCollection _errors = new(); /// /// Field used for the NoCommonParameter parameter. @@ -63,14 +63,6 @@ public class ShowCommandCommand : PSCmdlet, IDisposable private object _commandViewModelObj; #endregion - /// - /// Finalizes an instance of the ShowCommandCommand class. - /// - ~ShowCommandCommand() - { - this.Dispose(false); - } - #region Input Cmdlet Parameter /// /// Gets or sets the command name. @@ -83,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; } /// @@ -182,7 +174,7 @@ protected override void BeginProcessing() if (_showCommandProxy.ScreenHeight < this.Height) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Height", _showCommandProxy.ScreenHeight)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, @@ -192,7 +184,7 @@ protected override void BeginProcessing() if (_showCommandProxy.ScreenWidth < this.Width) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new NotSupportedException(string.Format(CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.PropertyValidate, "Width", _showCommandProxy.ScreenWidth)), "PARAMETER_DATA_ERROR", ErrorCategory.InvalidData, @@ -241,7 +233,7 @@ protected override void EndProcessing() return; } - StringBuilder errorString = new StringBuilder(); + StringBuilder errorString = new(); for (int i = 0; i < _errors.Count; i++) { @@ -276,10 +268,10 @@ protected override void StopProcessing() private void RunScriptSilentlyAndWithErrorHookup(string script) { // errors are not created here, because there is a field for it used in the final pop up - PSDataCollection output = new PSDataCollection(); + PSDataCollection output = new(); - output.DataAdded += new EventHandler(this.Output_DataAdded); - _errors.DataAdded += new EventHandler(this.Error_DataAdded); + output.DataAdded += this.Output_DataAdded; + _errors.DataAdded += this.Error_DataAdded; System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); ps.Streams.Error = _errors; @@ -294,7 +286,7 @@ private void RunScriptSilentlyAndWithErrorHookup(string script) /// private void IssueErrorForNoCommand() { - InvalidOperationException errorException = new InvalidOperationException( + InvalidOperationException errorException = new( string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.CommandNotFound, @@ -307,7 +299,7 @@ private void IssueErrorForNoCommand() /// private void IssueErrorForMoreThanOneCommand() { - InvalidOperationException errorException = new InvalidOperationException( + InvalidOperationException errorException = new( string.Format( CultureInfo.CurrentUICulture, FormatAndOut_out_gridview.MoreThanOneCommand, @@ -418,7 +410,7 @@ private bool CanProcessRecordForAllCommands() /// private void WaitForWindowClosedOrHelpNeeded() { - do + while (true) { int which = WaitHandle.WaitAny(new WaitHandle[] { _showCommandProxy.WindowClosed, _showCommandProxy.HelpNeeded, _showCommandProxy.ImportModuleNeeded }); @@ -452,7 +444,6 @@ private void WaitForWindowClosedOrHelpNeeded() _showCommandProxy.ImportModuleDone(_importedModules, _commands); continue; } - while (true); } /// 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 c0226471bdd..e2ba41fb3fc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandCommandInfo.cs @@ -14,17 +14,15 @@ namespace Microsoft.PowerShell.Commands.ShowCommandExtension public class ShowCommandCommandInfo { /// - /// Creates an instance of the ShowCommandCommandInfo class based on a CommandInfo object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// public ShowCommandCommandInfo(CommandInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.ModuleName = other.ModuleName; @@ -38,7 +36,7 @@ public ShowCommandCommandInfo(CommandInfo other) { this.ParameterSets = other.ParameterSets - .Select(x => new ShowCommandParameterSetInfo(x)) + .Select(static x => new ShowCommandParameterSetInfo(x)) .ToList() .AsReadOnly(); } @@ -62,17 +60,15 @@ public ShowCommandCommandInfo(CommandInfo other) } /// - /// Creates an instance of the ShowCommandCommandInfo class based on a PSObject object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// 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; @@ -90,9 +86,9 @@ 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"] != null && other.Members["Module"].Value as PSObject != null) + if (other.Members["Module"]?.Value is PSObject) { this.Module = new ShowCommandModuleInfo(other.Members["Module"].Value as PSObject); } @@ -116,31 +112,31 @@ internal static IEnumerable GetObjectEnumerable(System.Collections.IEnum /// /// A string representing the definition of the command. /// - public string Name { get; private set; } + public string Name { get; } /// /// A string representing module the command belongs to. /// - public string ModuleName { get; private set; } + public string ModuleName { get; } /// /// A reference to the module the command came from. /// - public ShowCommandModuleInfo Module { get; private set; } + public ShowCommandModuleInfo Module { get; } /// /// An enumeration of the command types this command belongs to. /// - public CommandTypes CommandType { get; private set; } + public CommandTypes CommandType { get; } /// /// A string representing the definition of the command. /// - public string Definition { get; private set; } + public string Definition { get; } /// /// A string representing the definition of the command. /// - public ICollection ParameterSets { get; private set; } + public ICollection ParameterSets { get; } } } 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 457cf6d7028..f31bc93525d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandModuleInfo.cs @@ -12,33 +12,29 @@ namespace Microsoft.PowerShell.Commands.ShowCommandExtension public class ShowCommandModuleInfo { /// - /// Creates an instance of the ShowCommandModuleInfo class based on a CommandInfo object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// public ShowCommandModuleInfo(PSModuleInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; } /// - /// Creates an instance of the ShowCommandModuleInfo class based on a PSObject object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// public ShowCommandModuleInfo(PSObject other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Members["Name"].Value as string; } @@ -46,6 +42,6 @@ public ShowCommandModuleInfo(PSObject other) /// /// Gets the name of this module. /// - public string Name { get; private set; } + public string Name { get; } } } 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 665fa039929..9bf79c5bd76 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterInfo.cs @@ -14,17 +14,15 @@ namespace Microsoft.PowerShell.Commands.ShowCommandExtension public class ShowCommandParameterInfo { /// - /// Creates an instance of the ShowCommandParameterInfo class based on a CommandParameterInfo object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// public ShowCommandParameterInfo(CommandParameterInfo other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.Name = other.Name; this.IsMandatory = other.IsMandatory; @@ -32,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; @@ -41,6 +39,7 @@ public ShowCommandParameterInfo(CommandParameterInfo other) } /// + /// Initializes a new instance of the class. /// Creates an instance of the ShowCommandParameterInfo class based on a PSObject object. /// /// @@ -48,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); @@ -68,37 +64,37 @@ public ShowCommandParameterInfo(PSObject other) /// /// Gets the name of the parameter. /// - public string Name { get; private set; } + public string Name { get; } /// /// True if the parameter is dynamic, or false otherwise. /// - public bool IsMandatory { get; private set; } + public bool IsMandatory { get; } /// /// Gets whether the parameter can take values from the incoming pipeline object. /// - public bool ValueFromPipeline { get; private set; } + public bool ValueFromPipeline { get; } /// /// Gets the type of the parameter. /// - public ShowCommandParameterType ParameterType { get; private set; } + public ShowCommandParameterType ParameterType { get; } /// /// The possible values of this parameter. /// - public IList ValidParamSetValues { get; private set; } + public IList ValidParamSetValues { get; } /// /// Gets whether the parameter has a parameter set. /// - public bool HasParameterSet { get; private set; } + public bool HasParameterSet { get; } /// /// Gets the position in which the parameter can be specified on the command line /// if not named. If the returned value is int.MinValue then the parameter must be named. /// - public int Position { get; private set; } + public int Position { get; } } } 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 e3c4cbf2cb7..c5ec1c74c08 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterSetInfo.cs @@ -14,55 +14,51 @@ namespace Microsoft.PowerShell.Commands.ShowCommandExtension public class ShowCommandParameterSetInfo { /// - /// Creates an instance of the ShowCommandParameterSetInfo class based on a CommandParameterSetInfo object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// 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(); } /// - /// Creates an instance of the ShowCommandParameterSetInfo class based on a PSObject object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// 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(); } /// /// Gets the name of the parameter set. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets whether the parameter set is the default parameter set. /// - public bool IsDefault { get; private set; } + public bool IsDefault { get; } /// /// Gets the parameter information for the parameters in this parameter set. /// - public ICollection Parameters { get; private set; } + public ICollection Parameters { get; } } } 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 d7a3258d27b..01da285a1d7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandParameterType.cs @@ -13,17 +13,15 @@ namespace Microsoft.PowerShell.Commands.ShowCommandExtension public class ShowCommandParameterType { /// - /// Creates an instance of the ShowCommandParameterType class based on a Type object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// public ShowCommandParameterType(Type other) { - if (other == null) - { - throw new ArgumentNullException(nameof(other)); - } + ArgumentNullException.ThrowIfNull(other); this.FullName = other.FullName; if (other.IsEnum) @@ -42,17 +40,15 @@ public ShowCommandParameterType(Type other) } /// - /// Creates an instance of the ShowCommandParameterType class based on a Type object. + /// Initializes a new instance of the class + /// with the specified . /// /// /// The object to wrap. /// 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; @@ -74,32 +70,32 @@ public ShowCommandParameterType(PSObject other) /// /// The full name of the outermost type. /// - public string FullName { get; private set; } + public string FullName { get; } /// /// Whether or not this type is an enum. /// - public bool IsEnum { get; private set; } + public bool IsEnum { get; } /// /// Whether or not this type is an dictionary. /// - public bool ImplementsDictionary { get; private set; } + public bool ImplementsDictionary { get; } /// /// Whether or not this enum has a flag attribute. /// - public bool HasFlagAttribute { get; private set; } + public bool HasFlagAttribute { get; } /// /// Whether or not this type is an array type. /// - public bool IsArray { get; private set; } + public bool IsArray { get; } /// /// Gets the inner type, if this corresponds to an array type. /// - public ShowCommandParameterType ElementType { get; private set; } + public ShowCommandParameterType ElementType { get; } /// /// Whether or not this type is a string. @@ -148,6 +144,6 @@ public bool IsSwitch /// /// If this is an enum value, return the list of potential values. /// - public ArrayList EnumValues { get; private set; } + public ArrayList EnumValues { get; } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs index 4a5700bdf7a..ab8909bb590 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs @@ -16,13 +16,13 @@ namespace Microsoft.PowerShell.Commands /// Help show-command create WPF object and invoke WPF windows with the /// Microsoft.PowerShell.Commands.ShowCommandInternal.ShowCommandHelperhelp type defined in Microsoft.PowerShell.GraphicalHost.dll. /// - internal class ShowCommandProxy + internal sealed class ShowCommandProxy { private const string ShowCommandHelperName = "Microsoft.PowerShell.Commands.ShowCommandInternal.ShowCommandHelper"; - private ShowCommandCommand _cmdlet; + private readonly ShowCommandCommand _cmdlet; - private GraphicalHostReflectionWrapper _graphicalHostReflectionWrapper; + private readonly GraphicalHostReflectionWrapper _graphicalHostReflectionWrapper; internal ShowCommandProxy(ShowCommandCommand cmdlet) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs index 2203fbb20a7..3f40ec3439e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; @@ -170,7 +168,7 @@ private void ProcessMarkdownInfo(MarkdownInfo markdownInfo) try { - ProcessStartInfo startInfo = new ProcessStartInfo(); + ProcessStartInfo startInfo = new(); startInfo.FileName = tmpFilePath; startInfo.UseShellExecute = true; Process.Start(startInfo); @@ -226,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 5f66b39753f..365a669c186 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Sort-Object.cs @@ -53,14 +53,14 @@ public SwitchParameter Descending /// [Parameter(ParameterSetName = "Top", Mandatory = true)] [ValidateRange(1, int.MaxValue)] - public int Top { get; set; } = 0; + public int Top { get; set; } /// /// Gets or sets the number of items to return in a Bottom N sort. /// [Parameter(ParameterSetName = "Bottom", Mandatory = true)] [ValidateRange(1, int.MaxValue)] - public int Bottom { get; set; } = 0; + public int Bottom { get; set; } /// /// Moves unique entries to the front of the list. @@ -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) @@ -239,7 +238,7 @@ private int Heapify(List dataToSort, OrderByPropertyCompar /// protected override void EndProcessing() { - OrderByProperty orderByProperty = new OrderByProperty( + OrderByProperty orderByProperty = new( this, InputObjects, Property, !Descending, ConvertedCulture, CaseSensitive); var dataToProcess = orderByProperty.OrderMatrix; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index 4fe5c641ff9..839a0b7c051 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -23,7 +23,7 @@ public sealed class StartSleepCommand : PSCmdlet, IDisposable /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { if (_waitHandle != null) { @@ -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 @@ -64,7 +73,7 @@ public void Dispose() // object used for synchronizes pipeline thread and stop thread // access to waitHandle - private object _syncObject = new object(); + private readonly object _syncObject = new(); // this is set to true by stopProcessing private bool _stopping = false; @@ -76,16 +85,13 @@ private void Sleep(int milliSecondsToSleep) { lock (_syncObject) { - if (_stopping == false) + if (!_stopping) { _waitHandle = new ManualResetEvent(false); } } - if (_waitHandle != null) - { - _waitHandle.WaitOne(milliSecondsToSleep, true); - } + _waitHandle?.WaitOne(milliSecondsToSleep, true); } /// @@ -105,6 +111,26 @@ protected override void ProcessRecord() 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."); break; @@ -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 32048362b06..4a0f4831299 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 { @@ -111,6 +124,7 @@ protected override void BeginProcessing() // the values to be written } } + /// /// protected override void ProcessRecord() @@ -126,12 +140,15 @@ protected override void EndProcessing() _commandWrapper.ShutDown(); } - private void Dispose(bool isDisposing) + /// + /// Release all resources. + /// + public void Dispose() { if (!_alreadyDisposed) { _alreadyDisposed = true; - if (isDisposing && _commandWrapper != null) + if (_commandWrapper != null) { _commandWrapper.Dispose(); _commandWrapper = null; @@ -139,22 +156,6 @@ private void Dispose(bool isDisposing) } } - /// - /// Dispose method in IDisposable. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Finalizer. - /// - ~TeeObjectCommand() - { - Dispose(false); - } #region private private CommandWrapper _commandWrapper; private bool _alreadyDisposed; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs index 668c5072841..909cbff3c8f 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 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,24 +186,25 @@ 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 || e is SecurityException ) { - Exception exception = new Exception( + Exception exception = new( string.Format( CultureInfo.CurrentUICulture, TestJsonCmdletStrings.JsonSchemaFileOpenFailure, @@ -125,9 +214,17 @@ e is SecurityException } catch (Exception e) { - Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonSchema, e); + 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,40 +232,104 @@ 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.Hierarchical }); + result = evaluationResults.IsValid; + if (!result) { - result = false; - - Exception exception = new Exception(TestJsonCmdletStrings.InvalidJsonAgainstSchema); - - foreach (var message in errorMessages) - { - ErrorRecord errorRecord = new ErrorRecord(exception, "InvalidJsonAgainstSchema", ErrorCategory.InvalidData, null); - errorRecord.ErrorDetails = new ErrorDetails(message.ToString()); - WriteError(errorRecord); - } + ReportValidationErrors(evaluationResults); } } } + catch (JsonSchemaReferenceResolutionException jsonExc) + { + result = false; + + Exception exception = new(TestJsonCmdletStrings.InvalidJsonSchema, jsonExc); + WriteError(new ErrorRecord(exception, "InvalidJsonSchema", ErrorCategory.InvalidData, _jschema)); + } catch (Exception exc) { result = false; - Exception exception = new Exception(TestJsonCmdletStrings.InvalidJson, exc); + Exception exception = new(TestJsonCmdletStrings.InvalidJson, exc); WriteError(new ErrorRecord(exception, "InvalidJson", ErrorCategory.InvalidData, Json)); } WriteObject(result); } + + /// + /// Recursively reports validation errors from hierarchical evaluation results. + /// Skips nodes (and their children) where IsValid is true to avoid false positives + /// from constructs like OneOf or AnyOf. + /// + /// The evaluation result to process. + private void ReportValidationErrors(EvaluationResults evaluationResult) + { + // Skip this node and all children if validation passed + if (evaluationResult.IsValid) + { + return; + } + + // Report errors at this level + HandleValidationErrors(evaluationResult); + + // Recursively process child results + if (evaluationResult.HasDetails) + { + foreach (var nestedResult in evaluationResult.Details) + { + ReportValidationErrors(nestedResult); + } + } + } + + private void HandleValidationErrors(EvaluationResults evaluationResult) + { + if (!evaluationResult.HasErrors) + { + 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/TimeExpressionCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TimeExpressionCommand.cs index d7071a06605..24ed81995d1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TimeExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TimeExpressionCommand.cs @@ -25,19 +25,19 @@ public sealed class MeasureCommandCommand : PSCmdlet /// This parameter specifies the current pipeline object. /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// The script block to apply. /// [Parameter(Position = 0, Mandatory = true)] - public ScriptBlock Expression { set; get; } + public ScriptBlock Expression { get; set; } #endregion #region private members - private System.Diagnostics.Stopwatch _stopWatch = new System.Diagnostics.Stopwatch(); + private readonly System.Diagnostics.Stopwatch _stopWatch = new(); #endregion diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs index fad330212ed..6633a66f96f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnblockFile.cs @@ -6,14 +6,15 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +#if UNIX using System.Globalization; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Internal; using System.Runtime.InteropServices; +#else +using System.Management.Automation; +using System.Management.Automation.Internal; +#endif #endregion @@ -73,7 +74,7 @@ public string[] LiteralPath /// protected override void ProcessRecord() { - List pathsToProcess = new List(); + List pathsToProcess = new(); ProviderInfo provider = null; if (string.Equals(this.ParameterSetName, "ByLiteralPath", StringComparison.OrdinalIgnoreCase)) @@ -109,7 +110,7 @@ protected override void ProcessRecord() { if (!WildcardPattern.ContainsWildcardCharacters(path)) { - ErrorRecord errorRecord = new ErrorRecord(e, + ErrorRecord errorRecord = new(e, "FileNotFound", ErrorCategory.ObjectNotFound, path); @@ -136,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); @@ -146,10 +147,10 @@ protected override void ProcessRecord() foreach (string path in pathsToProcess) { - if(IsBlocked(path)) + if (IsBlocked(path)) { UInt32 result = RemoveXattr(path, MacBlockAttribute, RemovexattrFollowSymLink); - if(result != 0) + if (result != 0) { string errorMessage = string.Format(CultureInfo.CurrentUICulture, UnblockFileStrings.UnblockError, path); Exception e = new InvalidOperationException(errorMessage); @@ -179,7 +180,7 @@ private bool IsValidFileForUnblocking(string resolvedpath) { if (!System.IO.File.Exists(resolvedpath)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new System.IO.FileNotFoundException(resolvedpath), "FileNotFound", ErrorCategory.ObjectNotFound, @@ -196,9 +197,9 @@ private bool IsValidFileForUnblocking(string resolvedpath) } #if UNIX - private bool IsBlocked(string path) + private static bool IsBlocked(string path) { - uint valueSize = 1024; + const uint valueSize = 1024; IntPtr value = Marshal.AllocHGlobal((int)valueSize); try { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UnregisterEventCommand.cs index f89b3d6e4cc..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 @@ -97,7 +97,7 @@ protected override void ProcessRecord() (!WildcardPattern.ContainsWildcardCharacters(_sourceIdentifier)) && (!_foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( string.Format( System.Globalization.CultureInfo.CurrentCulture, @@ -111,7 +111,7 @@ protected override void ProcessRecord() else if ((SubscriptionId >= 0) && (!_foundMatch)) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new ArgumentException( string.Format( System.Globalization.CultureInfo.CurrentCulture, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs index d19810522d0..4a3198d2911 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-Data.cs @@ -24,18 +24,18 @@ public class UpdateData : PSCmdlet ParameterSetName = FileParameterSet)] [Alias("PSPath", "Path")] [ValidateNotNull] - public string[] AppendPath { set; get; } = Array.Empty(); + public string[] AppendPath { get; set; } = Array.Empty(); /// /// Files to prepend to the existing set. /// [Parameter(ParameterSetName = FileParameterSet)] [ValidateNotNull] - public string[] PrependPath { set; get; } = Array.Empty(); + public string[] PrependPath { get; set; } = Array.Empty(); private static void ReportWrongExtension(string file, string errorId, PSCmdlet cmdlet) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewInvalidOperationException(UpdateDataStrings.UpdateData_WrongExtension, file, "ps1xml"), errorId, ErrorCategory.InvalidArgument, @@ -45,7 +45,7 @@ private static void ReportWrongExtension(string file, string errorId, PSCmdlet c private static void ReportWrongProviderType(string providerId, string errorId, PSCmdlet cmdlet) { - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( PSTraceSource.NewInvalidOperationException(UpdateDataStrings.UpdateData_WrongProviderError, providerId), errorId, ErrorCategory.InvalidArgument, @@ -61,7 +61,7 @@ private static void ReportWrongProviderType(string providerId, string errorId, P /// internal static Collection Glob(string[] files, string errorId, PSCmdlet cmdlet) { - Collection retValue = new Collection(); + Collection retValue = new(); foreach (string file in files) { Collection providerPaths; 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 dbff09ef36a..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) @@ -129,7 +126,7 @@ protected override void EndProcessing() private Hashtable CreateHashtable() { - Hashtable hash = new Hashtable(2); + Hashtable hash = new(2); if (Add != null) { hash.Add("Add", Add); @@ -150,7 +147,7 @@ private Hashtable CreateHashtable() private PSListModifier CreatePSListModifier() { - PSListModifier listModifier = new PSListModifier(); + PSListModifier listModifier = new(); if (Add != null) { foreach (object obj in Add) @@ -180,8 +177,8 @@ private PSListModifier CreatePSListModifier() private ErrorRecord NewError(string errorId, string resourceId, object targetObject, params object[] args) { - ErrorDetails details = new ErrorDetails(this.GetType().Assembly, "UpdateListStrings", resourceId, args); - ErrorRecord errorRecord = new ErrorRecord( + ErrorDetails details = new(this.GetType().Assembly, "UpdateListStrings", resourceId, args); + ErrorRecord errorRecord = new( new InvalidOperationException(details.Message), errorId, ErrorCategory.InvalidOperation, 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 de7d2590176..b73d8570040 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs @@ -28,7 +28,7 @@ public class UpdateTypeDataCommand : UpdateData private const string DynamicTypeSet = "DynamicTypeSet"; private const string TypeDataSet = "TypeDataSet"; - private static object s_notSpecified = new object(); + private static readonly object s_notSpecified = new(); private static bool HasBeenSpecified(object obj) { @@ -50,13 +50,16 @@ private static bool HasBeenSpecified(object obj) System.Management.Automation.Runspaces.TypeData.CodeMethod, IgnoreCase = true)] public PSMemberTypes MemberType { + get + { + return _memberType; + } + set { _memberType = value; _isMemberTypeSet = true; } - - get { return _memberType; } } private string _memberName; @@ -67,9 +70,9 @@ public PSMemberTypes MemberType [ValidateNotNullOrEmpty] public string MemberName { - set { _memberName = value; } - get { return _memberName; } + + set { _memberName = value; } } private object _value1 = s_notSpecified; @@ -80,9 +83,9 @@ public string MemberName [Parameter(ParameterSetName = DynamicTypeSet)] public object Value { - set { _value1 = value; } - get { return _value1; } + + set { _value1 = value; } } private object _value2; @@ -94,9 +97,9 @@ public object Value [ValidateNotNull] public object SecondValue { - set { _value2 = value; } - get { return _value2; } + + set { _value2 = value; } } private Type _typeConverter; @@ -107,9 +110,9 @@ public object SecondValue [ValidateNotNull] public Type TypeConverter { - set { _typeConverter = value; } - get { return _typeConverter; } + + set { _typeConverter = value; } } private Type _typeAdapter; @@ -120,9 +123,9 @@ public Type TypeConverter [ValidateNotNull] public Type TypeAdapter { - set { _typeAdapter = value; } - get { return _typeAdapter; } + + set { _typeAdapter = value; } } /// @@ -132,9 +135,9 @@ public Type TypeAdapter [ValidateNotNullOrEmpty] public string SerializationMethod { - set { _serializationMethod = value; } - get { return _serializationMethod; } + + set { _serializationMethod = value; } } /// @@ -144,9 +147,9 @@ public string SerializationMethod [ValidateNotNull] public Type TargetTypeForDeserialization { - set { _targetTypeForDeserialization = value; } - get { return _targetTypeForDeserialization; } + + set { _targetTypeForDeserialization = value; } } /// @@ -157,9 +160,9 @@ public Type TargetTypeForDeserialization [ValidateRange(0, int.MaxValue)] public int SerializationDepth { - set { _serializationDepth = value; } - get { return _serializationDepth; } + + set { _serializationDepth = value; } } /// @@ -169,9 +172,9 @@ public int SerializationDepth [ValidateNotNullOrEmpty] public string DefaultDisplayProperty { - set { _defaultDisplayProperty = value; } - get { return _defaultDisplayProperty; } + + set { _defaultDisplayProperty = value; } } /// @@ -181,9 +184,9 @@ public string DefaultDisplayProperty [ValidateNotNull] public bool? InheritPropertySerializationSet { - set { _inheritPropertySerializationSet = value; } - get { return _inheritPropertySerializationSet; } + + set { _inheritPropertySerializationSet = value; } } /// @@ -193,9 +196,9 @@ public bool? InheritPropertySerializationSet [ValidateNotNullOrEmpty] public string StringSerializationSource { - set { _stringSerializationSource = value; } - get { return _stringSerializationSource; } + + set { _stringSerializationSource = value; } } /// @@ -206,9 +209,9 @@ public string StringSerializationSource [ValidateNotNullOrEmpty] public string[] DefaultDisplayPropertySet { - set { _defaultDisplayPropertySet = value; } - get { return _defaultDisplayPropertySet; } + + set { _defaultDisplayPropertySet = value; } } /// @@ -219,9 +222,9 @@ public string[] DefaultDisplayPropertySet [ValidateNotNullOrEmpty] public string[] DefaultKeyPropertySet { - set { _defaultKeyPropertySet = value; } - get { return _defaultKeyPropertySet; } + + set { _defaultKeyPropertySet = value; } } /// @@ -232,9 +235,9 @@ public string[] DefaultKeyPropertySet [ValidateNotNullOrEmpty] public string[] PropertySerializationSet { - set { _propertySerializationSet = value; } - get { return _propertySerializationSet; } + + set { _propertySerializationSet = value; } } // These members are represented as NoteProperty in types.ps1xml @@ -257,13 +260,13 @@ public string[] PropertySerializationSet /// The type name we want to update on. /// [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = DynamicTypeSet)] - [ArgumentToTypeNameTransformationAttribute()] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { - set { _typeName = value; } - get { return _typeName; } + + set { _typeName = value; } } private bool _force = false; @@ -274,9 +277,9 @@ public string TypeName [Parameter(ParameterSetName = TypeDataSet)] public SwitchParameter Force { - set { _force = value; } - get { return _force; } + + set { _force = value; } } #endregion dynamic type set @@ -291,9 +294,9 @@ public SwitchParameter Force [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = TypeDataSet)] public TypeData[] TypeData { - set { _typeData = value; } - get { return _typeData; } + + set { _typeData = value; } } #endregion strong type data set @@ -368,11 +371,11 @@ private void ProcessStrongTypeData() var errors = new ConcurrentBag(); this.Context.TypeTable.Update(type, errors, false); // Write out errors... - if (errors.Count > 0) + if (!errors.IsEmpty) { foreach (string s in errors) { - RuntimeException rte = new RuntimeException(s); + RuntimeException rte = new(s); this.WriteError(new ErrorRecord(rte, "TypesDynamicUpdateException", ErrorCategory.InvalidOperation, null)); } } @@ -411,7 +414,7 @@ private void ProcessDynamicType() ThrowTerminatingError(NewError("TargetTypeNameEmpty", UpdateDataStrings.TargetTypeNameEmpty, _typeName)); } - TypeData type = new TypeData(_typeName) { IsOverride = _force }; + TypeData type = new(_typeName) { IsOverride = _force }; GetMembers(type.Members); @@ -457,19 +460,19 @@ private void ProcessDynamicType() if (_defaultDisplayPropertySet != null) { - PropertySetData defaultDisplayPropertySet = new PropertySetData(_defaultDisplayPropertySet); + PropertySetData defaultDisplayPropertySet = new(_defaultDisplayPropertySet); type.DefaultDisplayPropertySet = defaultDisplayPropertySet; } if (_defaultKeyPropertySet != null) { - PropertySetData defaultKeyPropertySet = new PropertySetData(_defaultKeyPropertySet); + PropertySetData defaultKeyPropertySet = new(_defaultKeyPropertySet); type.DefaultKeyPropertySet = defaultKeyPropertySet; } if (_propertySerializationSet != null) { - PropertySetData propertySerializationSet = new PropertySetData(_propertySerializationSet); + PropertySetData propertySerializationSet = new(_propertySerializationSet); type.PropertySerializationSet = propertySerializationSet; } @@ -492,11 +495,11 @@ private void ProcessDynamicType() var errors = new ConcurrentBag(); this.Context.TypeTable.Update(type, errors, false); // Write out errors... - if (errors.Count > 0) + if (!errors.IsEmpty) { foreach (string s in errors) { - RuntimeException rte = new RuntimeException(s); + RuntimeException rte = new(s); this.WriteError(new ErrorRecord(rte, "TypesDynamicUpdateException", ErrorCategory.InvalidOperation, null)); } } @@ -570,7 +573,7 @@ private void GetMembers(Dictionary members) } } - private T GetParameterType(object sourceValue) + private static T GetParameterType(object sourceValue) { return (T)LanguagePrimitives.ConvertTo(sourceValue, typeof(T), CultureInfo.InvariantCulture); } @@ -690,7 +693,7 @@ private ScriptPropertyData GetScriptProperty() value2ScriptBlock = GetParameterType(_value2); } - ScriptPropertyData scriptProperty = new ScriptPropertyData(_memberName, value1ScriptBlock, value2ScriptBlock); + ScriptPropertyData scriptProperty = new(_memberName, value1ScriptBlock, value2ScriptBlock); return scriptProperty; } @@ -712,7 +715,7 @@ private CodePropertyData GetCodeProperty() value2CodeReference = GetParameterType(_value2); } - CodePropertyData codeProperty = new CodePropertyData(_memberName, value1CodeReference, value2CodeReference); + CodePropertyData codeProperty = new(_memberName, value1CodeReference, value2CodeReference); return codeProperty; } @@ -723,7 +726,7 @@ private ScriptMethodData GetScriptMethod() EnsureValue2HasNotBeenSpecified(); ScriptBlock method = GetParameterType(_value1); - ScriptMethodData scriptMethod = new ScriptMethodData(_memberName, method); + ScriptMethodData scriptMethod = new(_memberName, method); return scriptMethod; } @@ -734,7 +737,7 @@ private CodeMethodData GetCodeMethod() EnsureValue2HasNotBeenSpecified(); MethodInfo codeReference = GetParameterType(_value1); - CodeMethodData codeMethod = new CodeMethodData(_memberName, codeReference); + CodeMethodData codeMethod = new(_memberName, codeReference); return codeMethod; } @@ -746,10 +749,10 @@ private CodeMethodData GetCodeMethod() /// /// /// - private ErrorRecord NewError(string errorId, string template, object targetObject, params object[] args) + private static ErrorRecord NewError(string errorId, string template, object targetObject, params object[] args) { string message = string.Format(CultureInfo.CurrentCulture, template, args); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(message), errorId, ErrorCategory.InvalidOperation, @@ -789,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])); } } @@ -803,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); } } @@ -822,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)); } } @@ -846,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 { @@ -862,11 +861,11 @@ private void ProcessTypeFiles() Context.InitialSessionState.Types.Add(sste); // Write out any errors... - if (errors.Count > 0) + if (!errors.IsEmpty) { foreach (string s in errors) { - RuntimeException rte = new RuntimeException(s); + RuntimeException rte = new(s); this.WriteError(new ErrorRecord(rte, "TypesXmlUpdateException", ErrorCategory.InvalidOperation, null)); } @@ -969,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)); } } @@ -1053,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 { @@ -1115,19 +1113,25 @@ 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() - Dictionary> fileToIndexMap = new Dictionary>(StringComparer.OrdinalIgnoreCase); - List indicesToRemove = new List(); + Dictionary> fileToIndexMap = new(StringComparer.OrdinalIgnoreCase); + List indicesToRemove = new(); if (Context.InitialSessionState != null) { 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 @@ -1158,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 @@ -1209,7 +1210,7 @@ protected override void ProcessRecord() } Dbg.Assert(!string.IsNullOrEmpty(typeNameToRemove), "TypeNameToRemove should be not null and not empty at this point"); - TypeData type = new TypeData(typeNameToRemove); + TypeData type = new(typeNameToRemove); string removeTypeFormattedTarget = string.Format(CultureInfo.InvariantCulture, removeTypeTarget, typeNameToRemove); if (ShouldProcess(removeTypeFormattedTarget, removeTypeAction)) @@ -1219,11 +1220,11 @@ protected override void ProcessRecord() var errors = new ConcurrentBag(); Context.TypeTable.Update(type, errors, true); // Write out errors... - if (errors.Count > 0) + if (!errors.IsEmpty) { foreach (string s in errors) { - RuntimeException rte = new RuntimeException(s); + RuntimeException rte = new(s); this.WriteError(new ErrorRecord(rte, "TypesDynamicRemoveException", ErrorCategory.InvalidOperation, null)); } } @@ -1255,10 +1256,10 @@ protected override void EndProcessing() this.Context.TypeTable.ClearConsolidatedMembers(); } - private ErrorRecord NewError(string errorId, string template, object targetObject, params object[] args) + private static ErrorRecord NewError(string errorId, string template, object targetObject, params object[] args) { string message = string.Format(CultureInfo.CurrentCulture, template, args); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(message), errorId, ErrorCategory.InvalidOperation, @@ -1334,7 +1335,6 @@ protected override void ProcessRecord() ValidateTypeName(); Dictionary alltypes = Context.TypeTable.GetAllTypeData(); - Collection typedefs = new Collection(); foreach (string type in alltypes.Keys) { @@ -1342,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 aa4c2634266..1e3cc038750 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/UtilityCommon.cs @@ -5,10 +5,9 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation; -using System.Management.Automation.Runspaces; using System.Text; -[module: SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "Microsoft.PowerShell.Commands.ByteCollection")] +[module: SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix", Scope = "type", Target = "~T:Microsoft.PowerShell.Commands.ByteCollection")] namespace Microsoft.PowerShell.Commands { @@ -79,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. /// @@ -189,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 @@ -205,28 +199,28 @@ 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. /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] - public byte[] Bytes { get; private set; } + public byte[] Bytes { get; } /// /// Gets the path of the file whose contents are wrapped in the ByteCollection. /// - public string Path { get; private set; } + public string Path { get; } /// /// 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 . /// - public string Label { get; private set; } + public string Label { get; } private const int BytesPerLine = 16; @@ -242,7 +236,7 @@ public string HexBytes { if (_hexBytes == string.Empty) { - StringBuilder line = new StringBuilder(BytesPerLine * 3); + StringBuilder line = new(BytesPerLine * 3); foreach (var currentByte in Bytes) { @@ -268,7 +262,7 @@ public string Ascii { if (_ascii == string.Empty) { - StringBuilder ascii = new StringBuilder(BytesPerLine); + StringBuilder ascii = new(BytesPerLine); foreach (var currentByte in Bytes) { @@ -305,11 +299,11 @@ public override string ToString() // '16 + 3' comes from format "{0:X16} ". // '16' comes from '[Uint64]::MaxValue.ToString("X").Length'. - StringBuilder nextLine = new StringBuilder(16 + 3 + (BytesPerLine * 3)); - StringBuilder asciiEnd = new StringBuilder(BytesPerLine); + StringBuilder nextLine = new(16 + 3 + (BytesPerLine * 3)); + StringBuilder asciiEnd = new(BytesPerLine); // '+1' comes from 'result.Append(nextLine.ToString() + " " + asciiEnd.ToString());' below. - StringBuilder result = new StringBuilder(nextLine.Capacity + asciiEnd.Capacity + 1); + StringBuilder result = new(nextLine.Capacity + asciiEnd.Capacity + 1); if (Bytes.Length > 0) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 6739dbb38e5..a41b284f568 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; @@ -14,7 +13,6 @@ namespace Microsoft.PowerShell.Commands /// Base class for all variable commands. /// Because -Scope is defined in VariableCommandBase, all derived commands must implement -Scope. /// - public abstract class VariableCommandBase : PSCmdlet { #region Parameters @@ -24,6 +22,7 @@ public abstract class VariableCommandBase : PSCmdlet /// [Parameter] [ValidateNotNullOrEmpty] + [ArgumentCompleter(typeof(ScopeArgumentCompleter))] public string Scope { get; set; } #endregion parameters @@ -40,10 +39,7 @@ protected string[] IncludeFilters set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _include = value; } @@ -63,10 +59,7 @@ protected string[] ExcludeFilters set { - if (value == null) - { - value = Array.Empty(); - } + value ??= Array.Empty(); _exclude = value; } @@ -101,7 +94,7 @@ internal List GetMatchingVariables(string name, string lookupScope, { wasFiltered = false; - List result = new List(); + List result = new(); if (string.IsNullOrEmpty(name)) { @@ -260,10 +253,7 @@ public string[] Name set { - if (value == null) - { - value = new string[] { "*" }; - } + value ??= new string[] { "*" }; _name = value; } @@ -338,10 +328,7 @@ protected override void ProcessRecord() GetMatchingVariables(varName, Scope, out wasFiltered, /*quiet*/ false); matchingVariables.Sort( - delegate (PSVariable left, PSVariable right) - { - return 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) @@ -360,7 +347,7 @@ protected override void ProcessRecord() if (!matchFound && !wasFiltered) { ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); @@ -379,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 @@ -495,7 +483,7 @@ protected override void ProcessRecord() if (varFound != null) { SessionStateException sessionStateException = - new SessionStateException( + new( Name, SessionStateCategory.Variable, "VariableAlreadyExists", @@ -519,7 +507,7 @@ protected override void ProcessRecord() if (ShouldProcess(target, action)) { - PSVariable newVariable = new PSVariable(Name, Value, Option); + PSVariable newVariable = new(Name, Value, Option); if (_visibility != null) { @@ -705,6 +693,12 @@ public SwitchParameter PassThru private bool _passThru; + /// + /// Gets whether we will append to the variable if it exists. + /// + [Parameter] + public SwitchParameter Append { get; set; } + private bool _nameIsFormalParameter; private bool _valueIsFormalParameter; #endregion parameters @@ -723,6 +717,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); + } + } + } } /// @@ -738,6 +759,16 @@ protected override void ProcessRecord() { if (_nameIsFormalParameter && _valueIsFormalParameter) { + if (Append) + { + if (Value != AutomationNull.Value) + { + _valueList ??= new List(); + + _valueList.Add(Value); + } + } + return; } @@ -745,10 +776,7 @@ protected override void ProcessRecord() { if (Value != AutomationNull.Value) { - if (_valueList == null) - { - _valueList = new List(); - } + _valueList ??= new List(); _valueList.Add(Value); } @@ -771,7 +799,14 @@ protected override void EndProcessing() { if (_valueIsFormalParameter) { - SetVariable(Name, Value); + if (Append) + { + SetVariable(Name, _valueList); + } + else + { + SetVariable(Name, Value); + } } else { @@ -815,7 +850,7 @@ private void SetVariable(string[] varNames, object varValue) { // First look for existing variables to set. - List matchingVariables = new List(); + List matchingVariables = new(); bool wasFiltered = false; @@ -870,15 +905,12 @@ private void SetVariable(string[] varNames, object varValue) } PSVariable varToSet = - new PSVariable( + new( varName, newVarValue, newOptions); - if (Description == null) - { - Description = string.Empty; - } + Description ??= string.Empty; varToSet.Description = Description; @@ -1107,10 +1139,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) { @@ -1126,7 +1155,7 @@ protected override void ProcessRecord() // characters were specified, write an error. ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); @@ -1288,7 +1317,7 @@ protected override void ProcessRecord() // characters were specified, write an error. ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( varName, "VariableNotFound", SessionStateStrings.VariableNotFound); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs index 7aeb864e2ba..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 @@ -60,9 +60,9 @@ public int Timeout #endregion parameters - private AutoResetEvent _eventArrived = new AutoResetEvent(false); + private readonly AutoResetEvent _eventArrived = new(false); private PSEventArgs _receivedEvent = null; - private object _receivedEventLock = new object(); + private readonly object _receivedEventLock = new(); private WildcardPattern _matchPattern; /// 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 1d33957d624..ea650e80e67 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs @@ -1,15 +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; using System.Net.Http; using System.Text; using System.Text.RegularExpressions; +using System.Threading; namespace Microsoft.PowerShell.Commands { @@ -18,37 +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 /// - /// Constructor for BasicHtmlWebResponseObject. + /// 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) { } /// - /// Constructor for HtmlWebResponseObject with memory stream. + /// 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); } @@ -62,7 +55,7 @@ public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentSt /// /// Content of the response body, decoded using , /// if the Content-Type response header is a recognized text - /// type. Otherwise null. + /// type. Otherwise . /// public new string Content { get; private set; } @@ -71,11 +64,11 @@ public BasicHtmlWebResponseObject(HttpResponseMessage response, Stream contentSt /// /// /// Encoding of the response body from the Content-Type header, - /// or null if the encoding could not be determined. + /// 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 . @@ -86,13 +79,11 @@ public WebCmdletElementCollection InputFields { if (_inputFields == null) { - EnsureHtmlParser(); - - List parsedFields = new List(); - MatchCollection fieldMatch = s_inputFieldRegex.Matches(Content); - foreach (Match field in fieldMatch) + List parsedFields = new(); + MatchCollection fieldMatch = HtmlParser.InputFieldRegex.Matches(Content); + foreach (Match match in fieldMatch) { - parsedFields.Add(CreateHtmlObject(field.Value, "INPUT")); + parsedFields.Add(CreateHtmlObject(match.Value, "INPUT")); } _inputFields = new WebCmdletElementCollection(parsedFields); @@ -102,7 +93,7 @@ public WebCmdletElementCollection InputFields } } - private WebCmdletElementCollection _links; + private WebCmdletElementCollection? _links; /// /// Gets the HTML a link elements parsed from . @@ -113,10 +104,8 @@ public WebCmdletElementCollection Links { if (_links == null) { - EnsureHtmlParser(); - - List parsedLinks = new List(); - MatchCollection linkMatch = s_linkRegex.Matches(Content); + List parsedLinks = new(); + MatchCollection linkMatch = HtmlParser.LinkRegex.Matches(Content); foreach (Match link in linkMatch) { parsedLinks.Add(CreateHtmlObject(link.Value, "A")); @@ -129,7 +118,7 @@ public WebCmdletElementCollection Links } } - private WebCmdletElementCollection _images; + private WebCmdletElementCollection? _images; /// /// Gets the HTML img elements parsed from . @@ -140,10 +129,8 @@ public WebCmdletElementCollection Images { if (_images == null) { - EnsureHtmlParser(); - - List parsedImages = new List(); - MatchCollection imageMatch = s_imageRegex.Matches(Content); + List parsedImages = new(); + MatchCollection imageMatch = HtmlParser.ImageRegex.Matches(Content); foreach (Match image in imageMatch) { parsedImages.Add(CreateHtmlObject(image.Value, "IMG")); @@ -163,32 +150,28 @@ 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; } } - private PSObject CreateHtmlObject(string html, string tagName) + private static PSObject CreateHtmlObject(string html, string tagName) { - PSObject elementObject = new PSObject(); + PSObject elementObject = new(); elementObject.Properties.Add(new PSNoteProperty("outerHTML", html)); elementObject.Properties.Add(new PSNoteProperty("tagName", tagName)); @@ -198,76 +181,37 @@ private PSObject CreateHtmlObject(string html, string tagName) return elementObject; } - private 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 void ParseAttributes(string outerHtml, PSObject elementObject) + private static void ParseAttributes(string outerHtml, PSObject elementObject) { // We might get an empty input for a directive from the HTML file if (!string.IsNullOrEmpty(outerHtml)) { // 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; @@ -287,5 +231,21 @@ private 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 8e28c630e17..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,89 +1,51 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; -using System.Linq; +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) { - StringBuilder raw = new StringBuilder(); + StringBuilder raw = new(); string protocol = WebResponseHelper.GetProtocol(response); if (!string.IsNullOrEmpty(protocol)) { 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 == null ? null : response.Content.Headers + response.Content.Headers }; foreach (var headerCollection in headerCollections) @@ -96,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}"); } } } @@ -110,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"; } } } @@ -185,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..903ff4d8f80 --- /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 273fae835c3..22ffaef288c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs @@ -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; @@ -110,8 +215,9 @@ private bool TryProcessFeedStream(Stream responseStream) if (isRssOrFeed) { - XmlDocument workingDocument = new XmlDocument(); - // performing a Read() here to avoid rrechecking + XmlDocument workingDocument = new(); + + // 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); @@ -143,9 +252,9 @@ private bool TryProcessFeedStream(Stream responseStream) } // Mostly cribbed from Serialization.cs#GetXmlReaderSettingsForCliXml() - private XmlReaderSettings GetSecureXmlReaderSettings() + private static XmlReaderSettings GetSecureXmlReaderSettings() { - XmlReaderSettings xrs = new XmlReaderSettings(); + XmlReaderSettings xrs = new(); xrs.CheckCharacters = false; xrs.CloseInput = false; @@ -159,14 +268,14 @@ private XmlReaderSettings GetSecureXmlReaderSettings() return xrs; } - private 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 bool TryConvertToXml(string xml, out object doc, ref Exception exRef) doc = null; } - return (doc != null); + return doc != null; } - private 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 bool TryConvertToJson(string json, out object obj, ref Exception exRef) 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 bool TryConvertToJson(string json, out object obj, ref Exception exRef) 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, /// @@ -251,52 +354,44 @@ public enum RestReturnType Xml, } - internal class BufferingStreamReader : Stream + internal sealed 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 Stream _baseStream; - private MemoryStream _streamBuffer; - private byte[] _copyBuffer; + 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 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 f38446ed57f..f1a455974b9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -7,23 +7,23 @@ using System.Collections.ObjectModel; using System.Globalization; using System.IO; -using System.Linq; using System.Management.Automation; 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; using System.Xml; -using Microsoft.Win32; - namespace Microsoft.PowerShell.Commands { /// @@ -63,29 +63,88 @@ 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. /// - Tls12 = SslProtocols.Tls12 + Tls12 = SslProtocols.Tls12, + + /// + /// Specifies the TLS 1.3 security protocol. The TLS protocol is defined in IETF RFC 8446. + /// + Tls13 = SslProtocols.Tls13 } /// /// 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 @@ -103,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 /// @@ -119,7 +190,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Alias("SV")] public virtual string SessionVariable { get; set; } - #endregion + #endregion Session #region Authorization and Credentials @@ -130,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. @@ -184,7 +255,7 @@ public abstract partial class WebRequestPSCmdlet : PSCmdlet [Parameter] public virtual SecureString Token { get; set; } - #endregion + #endregion Authorization and Credentials #region Headers @@ -201,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. @@ -214,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)] - public virtual int MaximumRetryCount { get; set; } = 0; + [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 @@ -255,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. @@ -271,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 @@ -291,7 +400,7 @@ public virtual string CustomMethod [Parameter(Mandatory = true, ParameterSetName = "StandardMethodNoProxy")] public virtual SwitchParameter NoProxy { get; set; } - #endregion + #endregion NoProxy #region Proxy @@ -317,7 +426,7 @@ public virtual string CustomMethod [Parameter(ParameterSetName = "CustomMethod")] public virtual SwitchParameter ProxyUseDefaultCredentials { get; set; } - #endregion + #endregion Proxy #region Input @@ -352,6 +461,7 @@ public virtual string CustomMethod /// Gets or sets the InFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string InFile { get; set; } /// @@ -359,7 +469,7 @@ public virtual string CustomMethod /// private string _originalFilePath; - #endregion + #endregion Input #region Output @@ -367,6 +477,7 @@ public virtual string CustomMethod /// Gets or sets the OutFile property. /// [Parameter] + [ValidateNotNullOrEmpty] public virtual string OutFile { get; set; } /// @@ -387,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; @@ -543,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 || null != Token) && Authentication != WebAuthenticationType.None) + else if ((Credential is not null || Token is not null) && Authentication != WebAuthenticationType.None) { ProcessAuthentication(); } @@ -602,16 +939,15 @@ internal virtual void PrepareSession() WebSession.UseDefaultCredentials = true; } - if (CertificateThumbprint != null) + if (CertificateThumbprint is not null) { - X509Store store = new X509Store(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 CryptographicException(WebCmdletStrings.ThumbprintNotFound); - throw ex; + throw new CryptographicException(WebCmdletStrings.ThumbprintNotFound); } foreach (X509Certificate2 tbCert in tbCollection) @@ -621,55 +957,76 @@ 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 WebProxy(Proxy); - webProxy.BypassProxyOnLocal = false; - if (ProxyCredential != null) - { - webProxy.Credentials = ProxyCredential.GetNetworkCredential(); - } - else if (ProxyUseDefaultCredentials) + WebSession.NoProxy = true; + } + else + { + if (Proxy is not null) { - // 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 (-1 < MaximumRedirection) + if (MaximumRedirection > -1) { 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 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(); } } @@ -679,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 UriBuilder(uri); - if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) - { - uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + FormatDictionary(bodyAsDictionary); - } - else - { - uriBuilder.Query = FormatDictionary(bodyAsDictionary); - } - - uri = uriBuilder.Uri; - // set body to null to prevent later FillRequestStream - Body = null; - } - - return uri; - } - - private 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 string FormatDictionary(IDictionary content) - { - if (content == null) - throw new ArgumentNullException(nameof(content)); - - StringBuilder bodyBuilder = new StringBuilder(); - foreach (string key in content.Keys) - { - if (0 < bodyBuilder.Length) - { - 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"); - } - - private bool IsCustomMethodSet() - { - return (ParameterSetName == "CustomMethod"); - } - 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)); + WebSession.ConnectionTimeout = ConvertTimeoutSecondsToTimeSpan(ConnectionTimeoutSeconds); } - 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 - { - /// - /// Constructor for HttpResponseException. - /// - /// 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; private set; } - } - - /// - /// 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()); - } - } - - #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 HttpClientHandler(); - handler.CookieContainer = WebSession.Cookies; + HttpClient client = WebSession.GetHttpClient(handleRedirect, out bool clientWasReset); - // 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) + if (clientWasReset) { - handler.Credentials = WebSession.Credentials; + WriteVerbose(WebCmdletStrings.WebSessionConnectionRecreated); } - 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 HttpClient(handler); - - // 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) - { - httpClient.Timeout = new TimeSpan(0, 0, TimeoutSec); - } - - 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(); @@ -1110,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; } @@ -1134,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); @@ -1148,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); @@ -1160,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. @@ -1199,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) + switch (content) { - 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 - { - 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)) { @@ -1281,8 +1258,8 @@ internal virtual void FillRequestStream(HttpRequestMessage request) } catch (FormatException ex) { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new ErrorRecord(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); + ValidationMetadataException outerEx = new(WebCmdletStrings.ContentTypeException, ex); + ErrorRecord er = new(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); ThrowTerminatingError(er); } } @@ -1290,106 +1267,97 @@ 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); + } + + // codeql[cs/ssrf] - This is expected Poweshell behavior where user inputted Uri is supported for the context of this method. The user assumes trust for the Uri and invocation is done on the user's machine, not a web application. If there is concern for remoting, they should use restricted remoting. + response = client.SendAsync(currentRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); - if (keepAuthorization && IsRedirectCode(response.StatusCode) && response.Headers.Location != null) + 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 (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(); @@ -1400,47 +1368,55 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM Resume = new SwitchParameter(false); 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); + { + FillRequestStream(requestWithoutRange); - 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. + // 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--; @@ -1452,220 +1428,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) { - int followedRelLink = 0; - Uri uri = Uri; - do + verboseBuilder.Append($" output to {QualifyFilePath(OutFile)}"); + } + + WriteVerbose(verboseBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebRequest Verbose Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebRequestDebugInfo(HttpRequestMessage request) + { + try + { + // Typical basic example: + // WebRequest Detail + // ---QUERY + // test = 5 + // --- HEADERS + // User - Agent: Mozilla / 5.0, (Linux;Ubuntu 24.04.2 LTS;en - US), PowerShell / 7.6.0 + StringBuilder debugBuilder = new("WebRequest Detail" + Environment.NewLine, 512); + + if (!string.IsNullOrEmpty(request.RequestUri?.Query)) + { + debugBuilder.Append(DebugHeaderPrefix).AppendLine("QUERY"); + string[] queryParams = request.RequestUri.Query.TrimStart('?').Split('&'); + debugBuilder + .AppendJoin(Environment.NewLine, queryParams) + .AppendLine() + .AppendLine(); + } + + debugBuilder.Append(DebugHeaderPrefix).AppendLine("HEADERS"); + + foreach (var headerSet in new HttpHeaders?[] { request.Headers, request.Content?.Headers }) + { + if (headerSet is null) { - 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, - request.RequestUri, - 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 HttpResponseException(message, response); - ErrorRecord er = new ErrorRecord(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 ErrorRecord(new InvalidOperationException(), "MaximumRedirectExceeded", ErrorCategory.InvalidOperation, request); - er.ErrorDetails = new ErrorDetails(WebCmdletStrings.MaximumRedirectionCountExceeded); - WriteError(er); - } - } - } - catch (HttpRequestException ex) - { - ErrorRecord er = new ErrorRecord(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 ErrorRecord(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 ErrorRecord(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); } /// @@ -1673,85 +1758,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) - { - if (!SkipHeaderValidation) - { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new ErrorRecord(outerEx, "WebCmdletContentTypeException", ErrorCategory.InvalidArgument, ContentType); - ThrowTerminatingError(er); - } - } - catch (ArgumentException ex) + catch (Exception ex) when (ex is FormatException || ex is ArgumentException) { if (!SkipHeaderValidation) { - var outerEx = new ValidationMetadataException(WebCmdletStrings.ContentTypeException, ex); - ErrorRecord er = new ErrorRecord(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 != null && (doc.FirstChild as XmlDeclaration) != null) + 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); } /// @@ -1759,65 +1822,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); @@ -1827,24 +1876,23 @@ 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 - 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; string rel = match.Groups["rel"].Value; if (url != string.Empty && rel != string.Empty && !_relationLink.ContainsKey(rel)) { - Uri absoluteUri = new Uri(requestUri, url); + Uri absoluteUri = new(requestUri, url); _relationLink.Add(rel, absoluteUri.AbsoluteUri); } } @@ -1854,18 +1902,15 @@ internal void ParseLinkHeader(HttpResponseMessage response, System.Uri requestUr } /// - /// Adds content to a . Object type detection is used to determine if the value is string, File, or Collection. + /// Adds content to a . Object type detection is used to determine if the value is string, File, or Collection. /// /// The Field Name to use. /// The Field Value to use. - /// 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) + /// The to update. + /// If true, collection types in will be enumerated. If false, collections will be treated as single value. + 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. @@ -1892,7 +1937,7 @@ private void AddMultipartContent(object fieldName, object fieldValue, MultipartF // Treat Strings and other single values as a StringContent. // If enumeration is false, also treat IEnumerables as StringContents. // String implements IEnumerable so the explicit check is required. - if (enumerate == false || fieldValue is string || !(fieldValue is IEnumerable)) + if (!enumerate || fieldValue is string || fieldValue is not IEnumerable) { formData.Add(GetMultipartStringContent(fieldName: fieldName, fieldValue: fieldValue)); return; @@ -1901,43 +1946,42 @@ 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); } } } /// - /// Gets a from the supplied field name and field value. Uses to convert the objects to strings. + /// Gets a from the supplied field name and field value. Uses to convert the objects to strings. /// - /// The Field Name to use for the - /// The Field Value to use for the - private StringContent GetMultipartStringContent(object fieldName, object fieldValue) + /// The Field Name to use for the + /// 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; } /// - /// Gets a from the supplied field name and . Uses to convert the fieldname to a string. + /// Gets a from the supplied field name and . Uses to convert the fieldname to a string. /// - /// The Field Name to use for the - /// The to use for the - private StreamContent GetMultipartStreamContent(object fieldName, Stream stream) + /// The Field Name to use for the + /// 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"); @@ -1945,18 +1989,137 @@ private StreamContent GetMultipartStreamContent(object fieldName, Stream stream) } /// - /// Gets a from the supplied field name and file. Calls to create the and then sets the file name. + /// Gets a from the supplied field name and file. Calls to create the and then sets the file name. /// - /// The Field Name to use for the - /// The file to use for the - private StreamContent GetMultipartFileContent(object fieldName, FileInfo file) + /// The Field Name to use for the + /// 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, FileAccess.Read, FileShare.Read)); + + 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 70c5721dbc1..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,103 +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 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 /// - /// Constructor for WebResponseObject. + /// 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) { } /// - /// Constructor for WebResponseObject with contentStream. + /// 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); } @@ -167,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; - if (0 >= contentLength) + 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 955786248f7..82e1277e00c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs @@ -27,18 +27,18 @@ public class ConvertFromJsonCommand : Cmdlet /// /// InputObjectBuffer buffers all InputObject contents available in the pipeline. /// - private List _inputObjectBuffer = new List(); + private readonly List _inputObjectBuffer = new(); /// /// 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 92e67f5681a..173d999b06d 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 CancellationTokenSource(); + 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; } @@ -72,28 +71,35 @@ public int Depth /// /// Specifies how strings are escaped when writing JSON text. /// If the EscapeHandling property is set to EscapeHtml, the result JSON string will - /// be returned with HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. + /// be returned with HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. /// [Parameter] public StringEscapeHandling EscapeHandling { get; set; } = StringEscapeHandling.Default; /// - /// Prerequisite checks. + /// IDisposable implementation, dispose of any disposable resources created by the cmdlet. + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + /// + /// Implementation of IDisposable for both manual Dispose() and finalizer-called disposal of resources. /// - protected override void BeginProcessing() + /// + /// Specified as true when Dispose() was called, false if this is called from the finalizer. + /// + protected virtual void Dispose(bool disposing) { - if (_depth > maxDepthAllowed) + if (disposing) { - string errorMessage = StringUtil.Format(WebCmdletStrings.ReachedMaximumDepthAllowed, maxDepthAllowed); - ThrowTerminatingError(new ErrorRecord( - new InvalidOperationException(errorMessage), - "ReachedMaximumDepthAllowed", - ErrorCategory.InvalidOperation, - null)); + _cancellationSource.Dispose(); } } - private List _inputObjects = new List(); + private readonly List _inputObjects = new(); /// /// Caching the input objects for the command. @@ -116,9 +122,9 @@ protected override void EndProcessing() Depth, EnumsAsStrings.IsPresent, Compress.IsPresent, - _cancellationSource.Token, EscapeHandling, - targetCmdlet: this); + targetCmdlet: this, + _cancellationSource.Token); // null is returned only if the pipeline is stopping (e.g. ctrl+c is signaled). // in that case, we shouldn't write the null to the output pipe. 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 b0f849490cd..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,16 +16,17 @@ 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 /// - /// Default constructor for InvokeWebRequestCommand. + /// Initializes a new instance of the class. /// 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 5cb4f2b4fde..c8fed859771 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs @@ -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 sealed class WebProxy : IWebProxy, IEquatable { - private ICredentials _credentials; - private Uri _proxyAddress; + 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 d1a4cde2115..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,27 +13,27 @@ namespace Microsoft.PowerShell.Commands public class FormObject { /// - /// Gets or private sets the Id property. + /// Gets the Id property. /// - public string Id { get; private set; } + public string Id { get; } /// - /// Gets or private sets the Method property. + /// Gets the Method property. /// - public string Method { get; private set; } + public string Method { get; } /// - /// Gets or private sets the Action property. + /// Gets the Action property. /// - public string Action { get; private set; } + public string Action { get; } /// - /// Gets or private sets the Fields property. + /// Gets the Fields property. /// - public Dictionary Fields { get; private set; } + public Dictionary Fields { get; } /// - /// Constructor for FormObject. + /// Initializes a new instance of the class. /// /// /// @@ -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 2b15f150984..6506f2bd2ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -8,8 +8,8 @@ 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; using Newtonsoft.Json; @@ -68,7 +68,7 @@ public readonly struct ConvertToJsonContext /// Indicates whether to use enum names for the JSON conversion. /// Indicates whether to get the compressed output. public ConvertToJsonContext(int maxDepth, bool enumsAsStrings, bool compressOutput) - : this(maxDepth, enumsAsStrings, compressOutput, CancellationToken.None, StringEscapeHandling.Default, targetCmdlet: null) + : this(maxDepth, enumsAsStrings, compressOutput, StringEscapeHandling.Default, targetCmdlet: null, CancellationToken.None) { } @@ -78,16 +78,16 @@ public ConvertToJsonContext(int maxDepth, bool enumsAsStrings, bool compressOutp /// The maximum depth to visit the object. /// Indicates whether to use enum names for the JSON conversion. /// Indicates whether to get the compressed output. - /// Specifies the cancellation token for cancelling the operation. /// Specifies how strings are escaped when writing JSON text. /// Specifies the cmdlet that is calling this method. + /// Specifies the cancellation token for cancelling the operation. public ConvertToJsonContext( int maxDepth, bool enumsAsStrings, bool compressOutput, - CancellationToken cancellationToken, StringEscapeHandling stringEscapeHandling, - PSCmdlet targetCmdlet) + PSCmdlet targetCmdlet, + CancellationToken cancellationToken) { this.MaxDepth = maxDepth; this.CancellationToken = cancellationToken; @@ -98,7 +98,7 @@ public ConvertToJsonContext( } } - private class DuplicateMemberHashSet : HashSet + private sealed class DuplicateMemberHashSet : HashSet { public DuplicateMemberHashSet(int capacity) : base(capacity, StringComparer.OrdinalIgnoreCase) @@ -151,35 +151,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, @@ -255,11 +288,11 @@ private static PSObject PopulateFromJDictionary(JObject entries, DuplicateMember return null; } - // Array switch (entry.Value) { case JArray list: { + // Array var listResult = PopulateFromJArray(list, out error); if (error != null) { @@ -299,52 +332,51 @@ private static ICollection PopulateFromJArray(JArray list, out ErrorReco { error = null; var result = new object[list.Count]; + var i = 0; - for (var index = 0; index < list.Count; index++) + foreach (var element in list) { - var element = list[index]; switch (element) { case JArray subList: + // Array + result[i++] = PopulateFromJArray(subList, out error); + if (error != null) { - // Array - var listResult = PopulateFromJArray(subList, out error); - if (error != null) - { - return null; - } - - result[index] = listResult; - break; + return null; } + + break; + case JObject dic: + // Dictionary + result[i++] = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); + if (error != null) { - // Dictionary - var dicResult = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); - if (error != null) - { - return null; - } - - result[index] = dicResult; - break; + return null; } + + break; + case JValue value: + if (value.Type != JTokenType.Comment) { - result[index] = value.Value; - break; + result[i++] = value.Value; } + + break; } } - return result; + // In the common case of not having any comments, return the original array, otherwise create a sliced copy. + return i == list.Count ? result : result[..i]; } // This function is a clone of PopulateFromDictionary using JObject as an input. private static Hashtable PopulateHashTableFromJDictionary(JObject entries, out ErrorRecord error) { error = null; - Hashtable result = new Hashtable(entries.Count); + OrderedHashtable result = new(entries.Count); foreach (var entry in entries) { // Case sensitive duplicates should normally not occur since JsonConvert.DeserializeObject @@ -402,46 +434,44 @@ private static ICollection PopulateHashTableFromJArray(JArray list, out { error = null; var result = new object[list.Count]; + var i = 0; - for (var index = 0; index < list.Count; index++) + foreach (var element in list) { - var element = list[index]; - switch (element) { - case JArray array: + case JArray subList: + // Array + result[i++] = PopulateHashTableFromJArray(subList, out error); + if (error != null) { - // Array - var listResult = PopulateHashTableFromJArray(array, out error); - if (error != null) - { - return null; - } - - result[index] = listResult; - break; + return null; } + + break; + case JObject dic: + // Dictionary + result[i++] = PopulateHashTableFromJDictionary(dic, out error); + if (error != null) { - // Dictionary - var dicResult = PopulateHashTableFromJDictionary(dic, out error); - if (error != null) - { - return null; - } - - result[index] = dicResult; - break; + return null; } + + break; + case JValue value: + if (value.Type != JTokenType.Comment) { - result[index] = value.Value; - break; + result[i++] = value.Value; } + + break; } } - return result; + // In the common case of not having any comments, return the original array, otherwise create a sliced copy. + return i == list.Count ? result : result[..i]; } #endregion ConvertFromJson @@ -457,6 +487,7 @@ public static string ConvertToJson(object objectToProcess, in ConvertToJsonConte { // Pre-process the object so that it serializes the same, except that properties whose // values cannot be evaluated are treated as having the value null. + _maxDepthWarningWritten = false; object preprocessedObject = ProcessValue(objectToProcess, currentDepth: 0, in context); var jsonSettings = new JsonSerializerSettings { @@ -484,6 +515,8 @@ public static string ConvertToJson(object objectToProcess, in ConvertToJsonConte } } + private static bool _maxDepthWarningWritten; + /// /// Return an alternate representation of the specified object that serializes the same JSON, except /// that properties that cannot be evaluated are treated as having the value null. @@ -527,7 +560,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; } @@ -539,7 +573,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; } @@ -548,7 +582,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(); } @@ -561,6 +595,16 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso { if (currentDepth > context.MaxDepth) { + if (!_maxDepthWarningWritten && context.Cmdlet != null) + { + _maxDepthWarningWritten = true; + string maxDepthMessage = string.Format( + CultureInfo.CurrentCulture, + WebCmdletStrings.JsonMaxDepthReached, + context.MaxDepth); + context.Cmdlet.WriteWarning(maxDepthMessage); + } + if (pso != null && pso.ImmediateBaseObjectIsEmpty) { // The obj is a pure PSObject, we convert the original PSObject to a string, @@ -577,15 +621,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); } @@ -620,9 +662,7 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso /// private static object AddPsProperties(object psObj, object obj, int depth, bool isPurePSObj, bool isCustomObj, in ConvertToJsonContext context) { - PSObject pso = psObj as PSObject; - - if (pso == null) + if (psObj is not PSObject pso) { return obj; } @@ -634,9 +674,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(); @@ -645,7 +684,7 @@ private static object AddPsProperties(object psObj, object obj, int depth, bool AppendPsProperties(pso, dict, depth, isCustomObj, in context); - if (wasDictionary == false && dict.Count == 1) + if (!wasDictionary && dict.Count == 1) { return obj; } @@ -667,6 +706,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, @@ -697,7 +742,7 @@ private static void AppendPsProperties(PSObject psObj, IDictionary receiver, int /// private static object ProcessDictionary(IDictionary dict, int depth, in ConvertToJsonContext context) { - Dictionary result = new Dictionary(dict.Count); + Dictionary result = new(dict.Count); foreach (DictionaryEntry entry in dict) { @@ -734,7 +779,7 @@ private static object ProcessDictionary(IDictionary dict, int depth, in ConvertT /// private static object ProcessEnumerable(IEnumerable enumerable, int depth, in ConvertToJsonContext context) { - List result = new List(); + List result = new(); foreach (object o in enumerable) { @@ -754,7 +799,7 @@ private static object ProcessEnumerable(IEnumerable enumerable, int depth, in Co /// private static object ProcessCustomObject(object o, int depth, in ConvertToJsonContext context) { - Dictionary result = new Dictionary(); + Dictionary result = new(); Type t = o.GetType(); foreach (FieldInfo info in t.GetFields(BindingFlags.Public | BindingFlags.Instance)) 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 a730c043216..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,13 +56,13 @@ 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 - Regex pattern = new Regex(@"\d+(\.\d+)+"); + // Find the version in the windows operating system description + Regex pattern = new(@"\d+(\.\d+)+"); string versionText = pattern.Match(OS).Value; - Version windowsPlatformversion = new Version(versionText); + Version windowsPlatformversion = new(versionText); s_windowsUserAgent = $"Windows NT {windowsPlatformversion.Major}.{windowsPlatformversion.Minor}"; } @@ -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 58d63c36ca3..d24961834b6 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; @@ -20,69 +22,50 @@ namespace Microsoft.PowerShell.Commands /// this class as a wrapper to MemoryStream to lazily initialize. Otherwise, the /// content will unnecessarily be read even if there are no consumers for it. /// - internal class WebResponseContentMemoryStream : MemoryStream + internal sealed class WebResponseContentMemoryStream : MemoryStream { #region Data - private Stream _originalStreamToProxy; + 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 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; /// /// @@ -103,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); } @@ -128,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); } @@ -179,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); } @@ -208,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 ProgressRecord(StreamHelper.ActivityId, WebCmdletStrings.ReadResponseProgressActivity, "statusDescriptionPlaceholder"); - for (int read = 1; 0 < read; totalLength += read) + ProgressRecord record = new(StreamHelper.ActivityId, WebCmdletStrings.ReadResponseProgressActivity, "statusDescriptionPlaceholder"); + 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) @@ -233,33 +232,111 @@ private void Initialize() } } - read = _originalStreamToProxy.Read(buffer, 0, buffer.Length); + read = _originalStreamToProxy.ReadAsync(buffer.AsMemory(), _perReadTimeout, cancellationToken).GetAwaiter().GetResult(); - if (0 < read) + if (read > 0) { base.Write(buffer, 0, read); } } - 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 @@ -268,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); - ProgressRecord record = new ProgressRecord( + 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); + } + } } /// @@ -317,18 +409,20 @@ 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 FileStream(filePath, fileMode, FileAccess.Write, FileShare.Read); - WriteToStream(stream, output, cmdlet, cancellationToken); + using FileStream output = new(filePath, fileMode, FileAccess.Write, FileShare.Read); + 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 StringBuilder(capacity: ChunkSize); + StringBuilder result = new(capacity: ChunkSize); Decoder decoder = encoding.GetDecoder(); int useBufferSize = 64; @@ -337,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 Regex( + 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 a28f07c842c..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. @@ -75,27 +116,48 @@ public class WebRequestSession public int RetryIntervalInSeconds { get; set; } /// - /// Construct a new instance of a WebRequestSession object. + /// Initializes a new instance of the class. /// 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 6f374336da5..d20d7d8712b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Write.cs @@ -21,7 +21,7 @@ public sealed class WriteDebugCommand : PSCmdlet [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] [AllowEmptyString] [Alias("Msg")] - public string Message { get; set; } = null; + public string Message { get; set; } /// /// This method implements the ProcessRecord method for Write-Debug command. @@ -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 DebugRecord(Message); - - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; + DebugRecord record = new(Message); - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -69,7 +65,7 @@ public sealed class WriteVerboseCommand : PSCmdlet [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] [AllowEmptyString] [Alias("Msg")] - public string Message { get; set; } = null; + public string Message { get; set; } /// /// This method implements the ProcessRecord method for Write-verbose command. @@ -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 VerboseRecord(Message); + VerboseRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -117,7 +109,7 @@ public sealed class WriteWarningCommand : PSCmdlet [Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)] [AllowEmptyString] [Alias("Msg")] - public string Message { get; set; } = null; + public string Message { get; set; } /// /// This method implements the ProcessRecord method for Write-Warning command. @@ -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 WarningRecord(Message); + WarningRecord record = new(Message); - InvocationInfo invocationInfo = GetVariableValue(SpecialVariables.MyInvocation) as InvocationInfo; - - if (invocationInfo != null) + if (GetVariableValue(SpecialVariables.MyInvocation) is InvocationInfo invocationInfo) { record.SetInvocationInfo(invocationInfo); } @@ -184,7 +172,7 @@ protected override void BeginProcessing() { if (tag.StartsWith("PS", StringComparison.OrdinalIgnoreCase)) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( new InvalidOperationException(StringUtil.Format(UtilityCommonStrings.PSPrefixReservedInInformationTag, tag)), "PSPrefixReservedInInformationTag", ErrorCategory.InvalidArgument, tag); ThrowTerminatingError(er); @@ -214,8 +202,8 @@ public class WriteOrThrowErrorCommand : PSCmdlet /// /// ErrorRecord.Exception -- if not specified, ErrorRecord.Exception is System.Exception. /// - [Parameter(ParameterSetName = "WithException", Mandatory = true)] - public Exception Exception { get; set; } = null; + [Parameter(Position = 0, ParameterSetName = "WithException", Mandatory = true)] + public Exception Exception { get; set; } /// /// If Exception is specified, this is ErrorRecord.ErrorDetails.Message; @@ -226,14 +214,14 @@ public class WriteOrThrowErrorCommand : PSCmdlet [AllowNull] [AllowEmptyString] [Alias("Msg")] - public string Message { get; set; } = null; + public string Message { get; set; } /// /// 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)] - public ErrorRecord ErrorRecord { get; set; } = null; + [Parameter(Position = 0, ParameterSetName = "ErrorRecord", Mandatory = true)] + public ErrorRecord ErrorRecord { get; set; } /// /// ErrorRecord.CategoryInfo.Category. @@ -254,7 +242,7 @@ public class WriteOrThrowErrorCommand : PSCmdlet /// [Parameter(ParameterSetName = "NoException")] [Parameter(ParameterSetName = "WithException")] - public object TargetObject { get; set; } = null; + public object TargetObject { get; set; } /// /// ErrorRecord.ErrorDetails.RecommendedAction. @@ -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; @@ -393,7 +374,7 @@ protected override void ProcessRecord() public sealed class WriteErrorCommand : WriteOrThrowErrorCommand { /// - /// Constructor. + /// Initializes a new instance of the class. /// public WriteErrorCommand() { @@ -428,12 +409,11 @@ public ThrowErrorCommand() /// when the user only specifies a string and not /// an Exception or ErrorRecord. /// - [Serializable] public class WriteErrorException : SystemException { #region ctor /// - /// Constructor for class WriteErrorException. + /// Initializes a new instance of the class. /// /// Constructed object. public WriteErrorException() @@ -442,7 +422,7 @@ public WriteErrorException() } /// - /// Constructor for class WriteErrorException. + /// Initializes a new instance of the class. /// /// /// Constructed object. @@ -452,7 +432,7 @@ public WriteErrorException(string message) } /// - /// Constructor for class WriteErrorException. + /// Initializes a new instance of the class. /// /// /// @@ -466,15 +446,16 @@ public WriteErrorException(string message, #region Serialization /// - /// Serialization constructor for class WriteErrorException. + /// Initializes a new instance of the class for serialization. /// /// 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 3dbb2e378ad..31670935fcf 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteAliasCommandBase.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Management.Automation; -using Dbg = System.Management.Automation; - namespace Microsoft.PowerShell.Commands { /// @@ -64,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 7c2f2e44bf9..48d84636ce2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Management.Automation; using System.Text; +using System.Xml; namespace Microsoft.PowerShell.Commands { @@ -19,7 +19,7 @@ public sealed class WriteHostCommand : ConsoleColorCmdlet /// [Parameter(Position = 0, ValueFromRemainingArguments = true, ValueFromPipeline = true)] [Alias("Msg", "Message")] - public object Object { get; set; } = null; + public object Object { get; set; } /// /// False to add a newline to the end of the output string, true if not. @@ -52,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) @@ -62,12 +60,16 @@ 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. bool printSeparator = false; - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); foreach (object element in enumerable) { @@ -103,7 +105,7 @@ protected override void ProcessRecord() { string result = ProcessObject(Object) ?? string.Empty; - HostInformationMessage informationMessage = new HostInformationMessage(); + HostInformationMessage informationMessage = new(); informationMessage.Message = result; informationMessage.NoNewLine = NoNewline.IsPresent; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs index 070c1275aa8..0751954c54e 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteProgressCmdlet.cs @@ -4,8 +4,6 @@ using System; using System.Management.Automation; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell.Commands { /// @@ -19,7 +17,6 @@ public sealed class WriteProgressCommand : PSCmdlet /// [Parameter( Position = 0, - Mandatory = true, HelpMessageBaseName = HelpMessageBaseName, HelpMessageResourceId = "ActivityParameterHelpMessage")] public string Activity { get; set; } @@ -38,8 +35,8 @@ public sealed class WriteProgressCommand : PSCmdlet /// Uniquely identifies this activity for purposes of chaining subordinate activities. /// [Parameter(Position = 2)] - [ValidateRange(0, Int32.MaxValue)] - public int Id { get; set; } = 0; + [ValidateRange(0, int.MaxValue)] + public int Id { get; set; } /// /// Percentage completion of the activity, or -1 if n/a. @@ -64,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; /// @@ -98,7 +95,29 @@ protected override void ProcessRecord() { - ProgressRecord pr = new ProgressRecord(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 8736965fd61..cc3b1f2b251 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs @@ -32,7 +32,7 @@ public sealed class ExportClixmlCommand : PSCmdlet, IDisposable /// [Parameter] [ValidateRange(1, int.MaxValue)] - public int Depth { get; set; } = 0; + public int Depth { get; set; } /// /// Mandatory file name to write to. @@ -111,10 +111,24 @@ public SwitchParameter NoClobber /// Encoding optional flag. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] - public Encoding Encoding { get; set; } = ClrFacade.GetDefaultEncoding(); + public Encoding Encoding + { + get + { + return _encoding; + } + + set + { + EncodingConversion.WarnIfObsolete(this, value); + _encoding = value; + } + } + + private Encoding _encoding = Encoding.Default; #endregion Command Line Parameters @@ -194,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( @@ -212,7 +229,7 @@ private void CreateFileStream() ); // create xml writer - XmlWriterSettings xmlSettings = new XmlWriterSettings(); + XmlWriterSettings xmlSettings = new(); xmlSettings.CloseOutput = true; xmlSettings.Encoding = sw.Encoding; xmlSettings.Indent = true; @@ -261,7 +278,7 @@ private void CreateFileStream() void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } @@ -314,13 +331,12 @@ public string[] LiteralPath private bool _disposed = false; /// - /// Public dispose method. + /// Release all resources. /// public void Dispose() { if (!_disposed) { - GC.SuppressFinalize(this); if (_helper != null) { _helper.Dispose(); @@ -374,7 +390,7 @@ public sealed class ConvertToXmlCommand : PSCmdlet, IDisposable /// [Parameter(HelpMessage = "Specifies how many levels of contained objects should be included in the XML representation")] [ValidateRange(1, int.MaxValue)] - public int Depth { get; set; } = 0; + public int Depth { get; set; } /// /// Input Object which is written to XML format. @@ -425,7 +441,7 @@ protected override void BeginProcessing() } else { - WriteObject(string.Format(CultureInfo.InvariantCulture, "", Encoding.UTF8.WebName)); + WriteObject(string.Create(CultureInfo.InvariantCulture, $"")); WriteObject(""); } } @@ -439,8 +455,7 @@ protected override void ProcessRecord() { CreateMemoryStream(); - if (_serializer != null) - _serializer.SerializeAsStream(InputObject); + _serializer?.SerializeAsStream(InputObject); if (_serializer != null) { @@ -449,7 +464,7 @@ protected override void ProcessRecord() } // Loading to the XML Document _ms.Position = 0; - StreamReader read = new StreamReader(_ms); + StreamReader read = new(_ms); string data = read.ReadToEnd(); WriteObject(data); @@ -458,8 +473,7 @@ protected override void ProcessRecord() } else { - if (_serializer != null) - _serializer.Serialize(InputObject); + _serializer?.Serialize(InputObject); } } @@ -484,13 +498,13 @@ protected override void EndProcessing() if (As.Equals("Document", StringComparison.OrdinalIgnoreCase)) { // this is a trusted xml doc - the cmdlet generated the doc into a private memory stream - XmlDocument xmldoc = new XmlDocument(); + XmlDocument xmldoc = new(); xmldoc.Load(_ms); WriteObject(xmldoc); } else if (As.Equals("String", StringComparison.OrdinalIgnoreCase)) { - StreamReader read = new StreamReader(_ms); + StreamReader read = new(_ms); string data = read.ReadToEnd(); WriteObject(data); } @@ -606,7 +620,7 @@ private void CleanUp() void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } @@ -617,10 +631,90 @@ 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. /// - internal class ImportXmlHelper : IDisposable + internal sealed class ImportXmlHelper : IDisposable { #region constructor @@ -633,7 +727,7 @@ internal class ImportXmlHelper : IDisposable /// Reference to cmdlet which is using this helper class. /// private readonly PSCmdlet _cmdlet; - private bool _isLiteralPath; + private readonly bool _isLiteralPath; internal ImportXmlHelper(string fileName, PSCmdlet cmdlet, bool isLiteralPath) { @@ -705,7 +799,7 @@ private void CleanUp() /// public void Dispose() { - if (_disposed == false) + if (!_disposed) { CleanUp(); } @@ -749,12 +843,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) { @@ -787,19 +879,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 @@ -822,7 +908,10 @@ public class SelectXmlCommand : PSCmdlet [Alias("PSPath", "LP")] public string[] LiteralPath { - get { return Path; } + get + { + return Path; + } set { @@ -878,7 +967,7 @@ private void WriteResults(XmlNodeList foundXmlNodes, string filePath) foreach (XmlNode foundXmlNode in foundXmlNodes) { - SelectXmlInfo selectXmlInfo = new SelectXmlInfo(); + SelectXmlInfo selectXmlInfo = new(); selectXmlInfo.Node = foundXmlNode; selectXmlInfo.Pattern = XPath; selectXmlInfo.Path = filePath; @@ -954,8 +1043,8 @@ private void WriteFileReadError(string filePath, Exception exception) filePath, exception.Message); - ArgumentException argumentException = new ArgumentException(errorMessage, exception); - ErrorRecord errorRecord = new ErrorRecord(argumentException, "ProcessingFile", ErrorCategory.InvalidArgument, filePath); + ArgumentException argumentException = new(errorMessage, exception); + ErrorRecord errorRecord = new(argumentException, "ProcessingFile", ErrorCategory.InvalidArgument, filePath); this.WriteError(errorRecord); } @@ -982,15 +1071,15 @@ private XmlNamespaceManager AddNameSpaceTable(string parametersetname, XmlDocume catch (NullReferenceException) { string message = StringUtil.Format(UtilityCommonStrings.SearchXMLPrefixNullError); - InvalidOperationException e = new InvalidOperationException(message); - ErrorRecord er = new ErrorRecord(e, "PrefixError", ErrorCategory.InvalidOperation, namespacetable); + InvalidOperationException e = new(message); + ErrorRecord er = new(e, "PrefixError", ErrorCategory.InvalidOperation, namespacetable); WriteError(er); } catch (ArgumentNullException) { string message = StringUtil.Format(UtilityCommonStrings.SearchXMLPrefixNullError); - InvalidOperationException e = new InvalidOperationException(message); - ErrorRecord er = new ErrorRecord(e, "PrefixError", ErrorCategory.InvalidOperation, namespacetable); + InvalidOperationException e = new(message); + ErrorRecord er = new(e, "PrefixError", ErrorCategory.InvalidOperation, namespacetable); WriteError(er); } } @@ -1019,7 +1108,7 @@ protected override void ProcessRecord() (ParameterSetName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)))) { // If any file not resolved, execution stops. this is to make consistent with select-string. - List fullresolvedPaths = new List(); + List fullresolvedPaths = new(); foreach (string fpath in Path) { if (_isLiteralPath) @@ -1035,8 +1124,8 @@ protected override void ProcessRecord() { // Cannot open File error string message = StringUtil.Format(UtilityCommonStrings.FileOpenError, provider.FullName); - InvalidOperationException e = new InvalidOperationException(message); - ErrorRecord er = new ErrorRecord(e, "ProcessingFile", ErrorCategory.InvalidOperation, fpath); + InvalidOperationException e = new(message); + ErrorRecord er = new(e, "ProcessingFile", ErrorCategory.InvalidOperation, fpath); WriteError(er); continue; } 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/MshHostTraceListener.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs index 9b225bdc141..b7e4ed279aa 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs @@ -4,7 +4,6 @@ using System; using System.Management.Automation; using System.Management.Automation.Internal.Host; -using System.Security.Permissions; using System.Text; namespace Microsoft.PowerShell.Commands @@ -18,13 +17,13 @@ namespace Microsoft.PowerShell.Commands /// This trace listener cannot be specified in the app.config file. /// It must be added through the add-tracelistener cmdlet. /// - internal class PSHostTraceListener + internal sealed class PSHostTraceListener : System.Diagnostics.TraceListener { #region TraceListener constructors and disposer /// - /// Default constructor used if no. + /// Initializes a new instance of the class. /// internal PSHostTraceListener(PSCmdlet cmdlet) : base(string.Empty) @@ -52,7 +51,6 @@ internal PSHostTraceListener(PSCmdlet cmdlet) /// /// True if the TraceListener is being disposed, false otherwise. /// - [SecurityPermission(SecurityAction.LinkDemand)] protected override void Dispose(bool disposing) { try @@ -76,7 +74,6 @@ protected override void Dispose(bool disposing) /// /// The trace output to be written. /// - [SecurityPermission(SecurityAction.LinkDemand)] public override void Write(string output) { try @@ -90,7 +87,7 @@ public override void Write(string output) } } - private StringBuilder _cachedWrite = new StringBuilder(); + private readonly StringBuilder _cachedWrite = new(); /// /// Sends the given output string to the host for processing. @@ -98,7 +95,6 @@ public override void Write(string output) /// /// The trace output to be written. /// - [SecurityPermission(SecurityAction.LinkDemand)] public override void WriteLine(string output) { try @@ -119,6 +115,6 @@ public override void WriteLine(string output) /// /// The host interface to write the debug line to. /// - private InternalHostUserInterface _ui; + private readonly InternalHostUserInterface _ui; } } 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 d05a535145d..9f8694ebd20 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/SetTracerCommand.cs @@ -34,7 +34,10 @@ public string[] Name [Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ParameterSetName = "optionsSet")] public PSTraceSourceOptions Option { - get { return base.OptionsInternal; } + get + { + return base.OptionsInternal; + } set { @@ -48,7 +51,10 @@ public PSTraceSourceOptions Option [Parameter(ParameterSetName = "optionsSet")] public TraceOptions ListenerOption { - get { return base.ListenerOptionsInternal; } + get + { + return base.ListenerOptionsInternal; + } set { @@ -93,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/TraceCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceCommandBase.cs index a2e04f79ae1..9118fd773fd 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceCommandBase.cs @@ -1,13 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell.Commands { /// @@ -59,7 +56,7 @@ internal Collection GetMatchingTraceSource( { notMatched = new Collection(); - Collection results = new Collection(); + Collection results = new(); foreach (string patternToMatch in patternsToMatch) { bool matchFound = false; @@ -105,12 +102,12 @@ internal Collection GetMatchingTraceSource( !WildcardPattern.ContainsWildcardCharacters(patternToMatch)) { ItemNotFoundException itemNotFound = - new ItemNotFoundException( + new( patternToMatch, "TraceSourceNotFound", SessionStateStrings.TraceSourceNotFound); - ErrorRecord errorRecord = new ErrorRecord(itemNotFound.ErrorRecord, itemNotFound); + ErrorRecord errorRecord = new(itemNotFound.ErrorRecord, itemNotFound); WriteError(errorRecord); } } 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 12f8e5b4e09..1eadd4934b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs @@ -10,8 +10,6 @@ using System.Management.Automation.Runspaces; using System.Threading; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell.Commands { /// @@ -27,7 +25,7 @@ public class TraceCommandCommand : TraceListenerCommandBase, IDisposable /// This parameter specifies the current pipeline object. /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// The TraceSource parameter determines which TraceSource categories the @@ -48,7 +46,10 @@ public string[] Name [Parameter(Position = 2)] public PSTraceSourceOptions Option { - get { return base.OptionsInternal; } + get + { + return base.OptionsInternal; + } set { @@ -84,7 +85,10 @@ public PSTraceSourceOptions Option [Parameter] public TraceOptions ListenerOption { - get { return base.ListenerOptionsInternal; } + get + { + return base.ListenerOptionsInternal; + } set { @@ -245,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 @@ -325,22 +323,15 @@ public void Dispose() /// cmdlet. It gets attached to the sub-pipelines success or error pipeline and redirects /// all objects written to these pipelines to trace-command pipeline. /// - internal class TracePipelineWriter : PipelineWriter + internal sealed class TracePipelineWriter : PipelineWriter { internal TracePipelineWriter( TraceListenerCommandBase cmdlet, 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; @@ -521,18 +512,16 @@ 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 PSCustomObject)) + if (baseObject is not PSCustomObject) { obj = baseObject; } } - ErrorRecord errorRecordResult = obj as ErrorRecord; - if (errorRecordResult != null) + if (obj is ErrorRecord errorRecordResult) { result = errorRecordResult; } @@ -540,9 +529,9 @@ private static ErrorRecord ConvertToErrorRecord(object obj) return result; } - private TraceListenerCommandBase _cmdlet; - private bool _writeError; + private readonly TraceListenerCommandBase _cmdlet; + private readonly bool _writeError; private bool _isOpen = true; - private Collection _matchingSources = new Collection(); + private readonly Collection _matchingSources = new(); } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs index 3eecdd51cba..f1e4fe2dfa0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceListenerCommandBase.cs @@ -10,8 +10,6 @@ using System.Management.Automation.Internal; using System.Security; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell.Commands { /// @@ -34,7 +32,10 @@ public class TraceListenerCommandBase : TraceCommandBase /// internal PSTraceSourceOptions OptionsInternal { - get { return _options; } + get + { + return _options; + } set { @@ -55,7 +56,10 @@ internal PSTraceSourceOptions OptionsInternal /// internal TraceOptions ListenerOptionsInternal { - get { return _traceOptions; } + get + { + return _traceOptions; + } set { @@ -231,7 +235,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc try { - Collection resolvedPaths = new Collection(); + Collection resolvedPaths = new(); try { // Resolve the file path @@ -289,7 +293,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc if (ForceWrite && System.IO.File.Exists(resolvedPath)) { // remove readonly attributes on the file - System.IO.FileInfo fInfo = new System.IO.FileInfo(resolvedPath); + System.IO.FileInfo fInfo = new(resolvedPath); if (fInfo != null) { // Save some disk write time by checking whether file is readonly.. @@ -302,13 +306,13 @@ internal void AddTraceListenersToSources(Collection matchingSourc } // Trace commands always append..So there is no need to set overwrite with force.. - FileStream fileStream = new FileStream(resolvedPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + FileStream fileStream = new(resolvedPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); FileStreams.Add(fileStream); // Open the file stream TextWriterTraceListener fileListener = - new TextWriterTraceListener(fileStream, resolvedPath); + new(fileStream, resolvedPath); fileListener.Name = FileListener; @@ -330,7 +334,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc if (fileOpenError != null) { ErrorRecord errorRecord = - new ErrorRecord( + new( fileOpenError, "FileListenerPathResolutionFailed", ErrorCategory.OpenError, @@ -355,7 +359,7 @@ internal void AddTraceListenersToSources(Collection matchingSourc if (error != null) { ErrorRecord errorRecord = - new ErrorRecord( + new( error, "FileListenerPathResolutionFailed", ErrorCategory.InvalidArgument, @@ -419,7 +423,7 @@ internal static void RemoveListenersByName( { TraceListener listenerToRemove = source.Listeners[index]; - if (fileListenersOnly && !(listenerToRemove is TextWriterTraceListener)) + if (fileListenersOnly && listenerToRemove is not TextWriterTraceListener) { // Since we only want to remove file listeners, skip any that // aren't file listeners @@ -493,7 +497,7 @@ internal void TurnOnTracing(Collection matchingSources, bool preC { // Copy the listeners into a different collection - Collection listenerCollection = new Collection(); + Collection listenerCollection = new(); foreach (TraceListener listener in source.Listeners) { listenerCollection.Add(listener); @@ -597,8 +601,8 @@ protected void ClearStoredState() _storedTraceSourceState.Clear(); } - private Dictionary>> _storedTraceSourceState = - new Dictionary>>(); + private readonly Dictionary>> _storedTraceSourceState = + new(); #endregion stored state } diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/AddTypeStrings.resx index ba570831de8..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. @@ -165,4 +147,13 @@ The specified reference assembly '{0}' is unnecessary and ignored. + + 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/CsvCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx index 0eff0d6f84f..dde1be6ab49 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx @@ -130,9 +130,6 @@ You must specify either the -UseQuotes or -QuoteFields parameters, but not both. - - You must specify either the -IncludeTypeInformation or -NoTypeInformation parameters, but not both. - You must specify either the -Path or -LiteralPath parameters, but not both. @@ -157,4 +154,7 @@ EOF is reached. + + You must specify either the -Append or -NoHeader parameters, but not both. + diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx index 1c36e4c8af0..043fa4a9320 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/Debugger.resx @@ -174,4 +174,7 @@ Wait-Debugger called on line {0} in {1}. + + A breakpoint associated with another runspace cannot be updated because there is no runspace with instance ID '{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 63b4271c382..8c8d59034cc 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/NewObjectStrings.resx @@ -144,7 +144,19 @@ Cannot create type. Only core types are supported in this language mode. - - {0} Please note that Single-Threaded Apartment is not supported in PowerShell. + + Cannot create type. Only core types are supported in {0} language mode on a policy locked down machine. + + + 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 397718a09bb..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. @@ -196,19 +193,19 @@ The cmdlet cannot run because the following conflicting parameters are specified: ProxyCredential and ProxyUseDefaultCredentials. Specify either ProxyCredential or ProxyUseDefaultCredentials, then retry. - 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 UseDefaultProxyCredentials parameters, then retry. + 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,16 +234,19 @@ Following rel link {0} - - {0} {1} with {2}-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} + + 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/Microsoft.PowerShell.ConsoleHost.csproj b/src/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj index 44d4fe48751..bbc8023b1da 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj +++ b/src/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj @@ -2,7 +2,7 @@ PowerShell Host - $(NoWarn);CS1570 + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.ConsoleHost diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs index 761c6f6e26f..8cc6bd8ab02 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/ComInterfaces.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -19,22 +18,22 @@ internal static class ComInterfaces /// string fields at all, nothing is lost. /// [StructLayout(LayoutKind.Sequential)] - internal struct StartUpInfo + 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; @@ -58,6 +57,7 @@ void GetPath( uint fFlags); void GetIDList(out IntPtr ppidl); + void SetIDList(IntPtr pidl); void GetDescription( @@ -83,8 +83,11 @@ void SetArguments( [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); void GetHotKey(out short wHotKey); + void SetHotKey(short wHotKey); + void GetShowCmd(out uint iShowCmd); + void SetShowCmd(uint iShowCmd); void GetIconLocation( @@ -138,7 +141,7 @@ internal interface IPropertyStore /// /// [PreserveSig] - HResult GetValue([In] ref PropertyKey key, [Out] PropVariant pv); + HResult GetValue([In] in PropertyKey key, [Out] PropVariant pv); /// /// Sets the value of a property in the store. @@ -147,7 +150,7 @@ internal interface IPropertyStore /// /// [PreserveSig] - HResult SetValue([In] ref PropertyKey key, [In] PropVariant pv); + HResult SetValue([In] in PropertyKey key, [In] PropVariant pv); /// /// Commits the changes. @@ -157,7 +160,7 @@ internal interface IPropertyStore HResult Commit(); } - [ComImport()] + [ComImport] [Guid("6332DEBF-87B5-4670-90C0-5E57B408A49E")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface ICustomDestinationList @@ -201,7 +204,7 @@ internal enum KnownDestinationCategory Recent } - [ComImport()] + [ComImport] [Guid("92CA9DCD-5622-4BBA-A805-5E9F541BD8C9")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IObjectArray @@ -214,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 @@ -235,6 +238,7 @@ void AddFromArray( [MarshalAs(UnmanagedType.Interface)] IObjectArray poaSource); void RemoveObject(uint uiIndex); + void Clear(); } @@ -244,15 +248,16 @@ 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); + void SetFlags(uint dwFlags); } diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs index e23f0810ea1..4789bcef06f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs @@ -4,18 +4,18 @@ namespace Microsoft.PowerShell { /// - /// HRESULT Wrapper - /// + /// HRESULT Wrapper + /// internal enum HResult { - /// - /// S_OK - /// + /// + /// S_OK + /// Ok = 0x0000, /// /// S_FALSE. - /// + /// False = 0x0001, /// diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs index 5c76d6051b8..408c59c482a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs @@ -19,10 +19,10 @@ internal sealed class PropVariant : IDisposable { // This is actually a VarEnum value, but the VarEnum type requires 4 bytes instead of the expected 2. [FieldOffset(0)] - private ushort _valueType; + private readonly ushort _valueType; [FieldOffset(8)] - private IntPtr _ptr; + private readonly IntPtr _ptr; /// /// Set a string value. @@ -34,9 +34,7 @@ internal PropVariant(string value) throw new ArgumentException("PropVariantNullString", nameof(value)); } -#pragma warning disable CS0618 // Type or member is obsolete (might get deprecated in future versions _valueType = (ushort)VarEnum.VT_LPWSTR; -#pragma warning restore CS0618 // Type or member is obsolete (might get deprecated in future versions _ptr = Marshal.StringToCoTaskMemUni(value); } @@ -51,14 +49,14 @@ public void Dispose() } /// - /// Finalizer. + /// Finalizes an instance of the class. /// ~PropVariant() { Dispose(); } - private class PropVariantNativeMethods + private static class PropVariantNativeMethods { [DllImport("Ole32.dll", PreserveSig = false)] internal static extern void PropVariantClear([In, Out] PropVariant pvar); diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs index be45b0d8d84..93346424dc5 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropertyKey.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell /// Defines a unique key for a Shell Property. /// [StructLayout(LayoutKind.Sequential, Pack = 4)] - internal struct PropertyKey : IEquatable + internal readonly struct PropertyKey : IEquatable { #region Public Properties /// @@ -21,7 +21,7 @@ internal struct PropertyKey : IEquatable /// /// Property identifier (PID) /// - public Int32 PropertyId { get; } + public int PropertyId { get; } #endregion @@ -32,7 +32,7 @@ internal struct PropertyKey : IEquatable /// /// 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; @@ -75,7 +75,7 @@ public override bool Equals(object obj) if (obj == null) return false; - if (!(obj is PropertyKey)) + if (obj is not PropertyKey) return false; PropertyKey other = (PropertyKey)obj; diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/TaskbarJumpList.cs index 5b7572c94b2..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 } @@ -59,8 +58,8 @@ private static void CreateElevatedEntry(string title) // Check startupInfo first to know if the current shell is interactive and owns a window before proceeding // This check is fast (less than 1ms) and allows for quick-exit GetStartupInfo(out StartUpInfo startupInfo); - var STARTF_USESHOWWINDOW = 0x00000001; - var SW_HIDE = 0; + const uint STARTF_USESHOWWINDOW = 0x00000001; + const ushort SW_HIDE = 0; if (((startupInfo.dwFlags & STARTF_USESHOWWINDOW) == 1) && (startupInfo.wShowWindow != SW_HIDE)) { string cmdPath = Assembly.GetEntryAssembly().Location.Replace(".dll", ".exe"); @@ -97,7 +96,7 @@ private static void CreateElevatedEntry(string title) flags |= 0x00002000; // SLDF_RUNAS_USER shellLinkDataList.SetFlags(flags); var PKEY_TITLE = new PropertyKey(new Guid("{F29F85E0-4FF9-1068-AB91-08002B27B3D9}"), 2); - hResult = nativePropertyStore.SetValue(ref PKEY_TITLE, new PropVariant(title)); + hResult = nativePropertyStore.SetValue(in PKEY_TITLE, new PropVariant(title)); if (hResult < 0) { pCustDestList.AbortList(); @@ -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 f65736a52dc..0fb85e740f4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable using System; using System.Collections.Generic; @@ -29,10 +30,8 @@ internal class NullHostUserInterface : PSHostUserInterface /// /// RawUI. /// - public override PSHostRawUserInterface RawUI - { - get { return null; } - } + public override PSHostRawUserInterface? RawUI + => null; /// /// Prompt. @@ -42,9 +41,7 @@ public override PSHostRawUserInterface RawUI /// /// public override Dictionary Prompt(string caption, string message, Collection descriptions) - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// PromptForChoice. @@ -55,9 +52,7 @@ public override Dictionary Prompt(string caption, string messa /// /// public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// PromptForCredential. @@ -68,9 +63,7 @@ public override int PromptForChoice(string caption, string message, Collection /// public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName) - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// PromptForCredential. @@ -83,38 +76,21 @@ public override PSCredential PromptForCredential(string caption, string message, /// /// public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options) - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// ReadLine. /// /// public override string ReadLine() - { - throw new PSNotImplementedException(); - } - - /// - /// Null implementation of ReadLineMaskedAsString. - /// - /// - /// It throws an exception. - /// - public override string ReadLineMaskedAsString() - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// ReadLineAsSecureString. /// /// public override SecureString ReadLineAsSecureString() - { - throw new PSNotImplementedException(); - } + => throw new PSNotImplementedException(); /// /// Write. @@ -144,9 +120,7 @@ public override void WriteDebugLine(string message) /// /// public override void WriteErrorLine(string value) - { - Console.Out.WriteLine(value); - } + => Console.Out.WriteLine(value); /// /// WriteLine. @@ -183,10 +157,24 @@ internal class CommandLineParameterParser private const int MaxPipePathLengthLinux = 108; private const int MaxPipePathLengthMacOS = 104; - internal static readonly string[] validParameters = { + internal static int MaxNameLength() + { + if (Platform.IsWindows) + { + return ushort.MaxValue; + } + + int maxLength = Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS; + return maxLength - Path.GetTempPath().Length; + } + + internal bool? InputRedirectedTestHook; + + private static readonly string[] s_validParameters = { "sta", "mta", "command", + "commandwithargs", "configurationname", "custompipename", "encodedcommand", @@ -199,6 +187,7 @@ internal class CommandLineParameterParser "nologo", "noninteractive", "noprofile", + "noprofileloadtime", "outputformat", "removeworkingdirectorytrailingcharacter", "settingsfile", @@ -207,36 +196,104 @@ internal class CommandLineParameterParser "workingdirectory" }; - internal CommandLineParameterParser(PSHostUserInterface hostUI, string bannerText, string helpText) +#pragma warning disable SA1025 // CodeMustNotContainMultipleWhitespaceInARow + /// + /// 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 { - if (hostUI == null) + Command = 0x0000000000000001, // -Command | -c + ConfigurationName = 0x0000000000000002, // -ConfigurationName | -config + CustomPipeName = 0x0000000000000004, // -CustomPipeName + EncodedCommand = 0x0000000000000008, // -EncodedCommand | -e | -ec + EncodedArgument = 0x0000000000000010, // -EncodedArgument + ExecutionPolicy = 0x0000000000000020, // -ExecutionPolicy | -ex | -ep + File = 0x0000000000000040, // -File | -f + Help = 0x0000000000000080, // -Help, -?, /? + InputFormat = 0x0000000000000100, // -InputFormat | -inp | -if + Interactive = 0x0000000000000200, // -Interactive | -i + Login = 0x0000000000000400, // -Login | -l + MTA = 0x0000000000000800, // -MTA + NoExit = 0x0000000000001000, // -NoExit | -noe + NoLogo = 0x0000000000002000, // -NoLogo | -nol + NonInteractive = 0x0000000000004000, // -NonInteractive | -noni + NoProfile = 0x0000000000008000, // -NoProfile | -nop + OutputFormat = 0x0000000000010000, // -OutputFormat | -o | -of + SettingsFile = 0x0000000000020000, // -SettingsFile | -settings + SSHServerMode = 0x0000000000040000, // -SSHServerMode | -sshs + SocketServerMode = 0x0000000000080000, // -SocketServerMode | -sockets + ServerMode = 0x0000000000100000, // -ServerMode | -server + NamedPipeServerMode = 0x0000000000200000, // -NamedPipeServerMode | -namedpipes + STA = 0x0000000000400000, // -STA + Version = 0x0000000000800000, // -Version | -v + WindowStyle = 0x0000000001000000, // -WindowStyle | -w + WorkingDirectory = 0x0000000002000000, // -WorkingDirectory | -wd + ConfigurationFile = 0x0000000004000000, // -ConfigurationFile + NoProfileLoadTime = 0x0000000008000000, // -NoProfileLoadTime + CommandWithArgs = 0x0000000010000000, // -CommandWithArgs | -cwa + + // Enum values for specified ExecutionPolicy + EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted + EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed + 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 + + // V2 Socket Server Mode + V2SocketServerMode = 0x0000100000000000, // -V2SocketServerMode | -v2so + } +#pragma warning restore SA1025 // CodeMustNotContainMultipleWhitespaceInARow + + internal ParameterBitmap ParametersUsed = 0; + + internal double ParametersUsedAsDouble + { + get { return (double)ParametersUsed; } + } + + [Conditional("DEBUG")] + private void AssertArgumentsParsed() + { + if (!_dirty) { - throw new PSArgumentNullException(nameof(hostUI)); + throw new InvalidOperationException("Parse has not been called yet"); } + } - _hostUI = hostUI; - - _bannerText = bannerText; - _helpText = helpText; + internal CommandLineParameterParser() + { } #region Internal properties + internal bool AbortStartup { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _abortStartup; } } - internal string InitialCommand + internal string? SettingsFile { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); + return _settingsFile; + } + } + internal string? InitialCommand + { + get + { + AssertArgumentsParsed(); return _commandLineCommand; } } @@ -245,17 +302,27 @@ internal bool WasInitialCommandEncoded { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _wasCommandEncoded; } } +#if !UNIX + internal ProcessWindowStyle? WindowStyle + { + get + { + AssertArgumentsParsed(); + return _windowStyle; + } + } +#endif + internal bool ShowBanner { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); return _showBanner; } } @@ -264,8 +331,7 @@ internal bool NoExit { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _noExit; } } @@ -274,8 +340,7 @@ internal bool SkipProfiles { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _skipUserInit; } } @@ -284,8 +349,7 @@ internal uint ExitCode { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _exitCode; } } @@ -294,8 +358,7 @@ internal bool ExplicitReadCommandsFromStdin { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _explicitReadCommandsFromStdin; } } @@ -304,8 +367,7 @@ internal bool NoPrompt { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _noPrompt; } } @@ -314,55 +376,126 @@ internal Collection Args { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _collectedArgs; } } - internal string ConfigurationName + internal string? ConfigurationFile + { + get + { + AssertArgumentsParsed(); + return _configurationFile; + } + } + + internal string? ConfigurationName { - get { return _configurationName; } + get + { + AssertArgumentsParsed(); + return _configurationName; + } + + set + { + if (!string.IsNullOrEmpty(value)) + { + _configurationName = value; + } + } } internal bool SocketServerMode { get { + AssertArgumentsParsed(); return _socketServerMode; } } internal bool NamedPipeServerMode { - get { return _namedPipeServerMode; } + get + { + AssertArgumentsParsed(); + return _namedPipeServerMode; + } } internal bool SSHServerMode { - get { return _sshServerMode; } + get + { + AssertArgumentsParsed(); + return _sshServerMode; + } } internal bool ServerMode { get { + AssertArgumentsParsed(); return _serverMode; } } + // Added for using in xUnit tests + internal string? ErrorMessage + { + get + { + AssertArgumentsParsed(); + return _error; + } + } + + // Added for using in xUnit tests + internal bool ShowShortHelp + { + get + { + AssertArgumentsParsed(); + return _showHelp; + } + } + + // Added for using in xUnit tests + internal bool ShowExtendedHelp + { + get + { + AssertArgumentsParsed(); + return _showExtendedHelp; + } + } + + internal bool NoProfileLoadTime + { + get + { + AssertArgumentsParsed(); + return _noProfileLoadTime; + } + } + internal bool ShowVersion { get { + AssertArgumentsParsed(); return _showVersion; } } - internal string CustomPipeName + internal string? CustomPipeName { get { + AssertArgumentsParsed(); return _customPipeName; } } @@ -371,8 +504,7 @@ internal Serialization.DataFormat OutputFormat { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _outFormat; } } @@ -381,8 +513,7 @@ internal bool OutputFormatSpecified { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); - + AssertArgumentsParsed(); return _outputFormatSpecified; } } @@ -391,48 +522,70 @@ internal Serialization.DataFormat InputFormat { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); return _inFormat; } } - internal string File + internal string? File { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); return _file; } } - internal string ExecutionPolicy + internal string? ExecutionPolicy { get { - Dbg.Assert(_dirty, "Parse has not been called yet"); + AssertArgumentsParsed(); return _executionPolicy; } } + internal bool StaMode + { + get + { + AssertArgumentsParsed(); + if (_staMode.HasValue) + { + return _staMode.Value; + } + else + { + return Platform.IsStaSupported; + } + } + } + internal bool ThrowOnReadAndPrompt { get { + AssertArgumentsParsed(); return _noInteractive; } } internal bool NonInteractive { - get { return _noInteractive; } + get + { + AssertArgumentsParsed(); + return _noInteractive; + } } - internal string WorkingDirectory + internal string? WorkingDirectory { get { + AssertArgumentsParsed(); #if !UNIX - if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory.Length > 0) + if (_removeWorkingDirectoryTrailingCharacter && _workingDirectory?.Length > 0) { return _workingDirectory.Remove(_workingDirectory.Length - 1); } @@ -444,7 +597,38 @@ internal string WorkingDirectory #if !UNIX internal bool RemoveWorkingDirectoryTrailingCharacter { - get { return _removeWorkingDirectoryTrailingCharacter; } + get + { + AssertArgumentsParsed(); + return _removeWorkingDirectoryTrailingCharacter; + } + } + + internal DateTimeOffset? UTCTimestamp + { + get + { + AssertArgumentsParsed(); + return _utcTimestamp; + } + } + + internal string? Token + { + get + { + AssertArgumentsParsed(); + return _token; + } + } + + internal bool V2SocketServerMode + { + get + { + AssertArgumentsParsed(); + return _v2SocketServerMode; + } } #endif @@ -460,57 +644,42 @@ internal bool RemoveWorkingDirectoryTrailingCharacter /// /// The index in args to the argument following '-SettingFile'. /// - /// - /// Used to allow the helper to write errors to the console. If not supplied, no errors will be written. - /// /// /// Returns true if the argument was parsed successfully and false if not. /// - private static bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex, CommandLineParameterParser parser) + private bool TryParseSettingFileHelper(string[] args, int settingFileArgIndex) { if (settingFileArgIndex >= args.Length) { - if (parser != null) - { - parser.WriteCommandLineError( - CommandLineParameterParserStrings.MissingSettingsFileArgument); - } - + SetCommandLineError(CommandLineParameterParserStrings.MissingSettingsFileArgument); return false; } - string configFile = null; + string configFile; try { configFile = NormalizeFilePath(args[settingFileArgIndex]); } catch (Exception ex) { - if (parser != null) - { - string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message); - parser.WriteCommandLineError(error); - } - + string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidSettingsFileArgument, args[settingFileArgIndex], ex.Message); + SetCommandLineError(error); return false; } if (!System.IO.File.Exists(configFile)) { - if (parser != null) - { - string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile); - parser.WriteCommandLineError(error); - } - + string error = string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.SettingsFileNotExists, configFile); + SetCommandLineError(error); return false; } - PowerShellConfig.Instance.SetSystemConfigFilePath(configFile); + _settingsFile = configFile; + return true; } - private static string GetConfigurationNameFromGroupPolicy() + internal static string GetConfigurationNameFromGroupPolicy() { // Current user policy takes precedence. var consoleSessionSetting = Utils.GetPolicySetting(Utils.CurrentUserThenSystemWideConfig); @@ -519,43 +688,6 @@ private static string GetConfigurationNameFromGroupPolicy() consoleSessionSetting.ConsoleSessionConfigurationName : string.Empty; } - /// - /// Processes the command line parameters to ConsoleHost which must be parsed before the Host is created. - /// Success to indicate that the program should continue running. - /// - /// - /// The command line parameters to be processed. - /// - internal static void EarlyParse(string[] args) - { - if (args == null) - { - Dbg.Assert(args != null, "Argument 'args' to EarlyParseHelper should never be null"); - return; - } - - bool noexitSeen = false; - for (int i = 0; i < args.Length; ++i) - { - (string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, parser: null, ref noexitSeen); - if (switchKeyResults.ShouldBreak) - { - break; - } - - string switchKey = switchKeyResults.SwitchKey; - - if (!string.IsNullOrEmpty(switchKey) && MatchSwitch(switchKey, match: "settingsfile", smallestUnambiguousMatch: "settings")) - { - // parse setting file arg and don't write error as there is no host yet. - if (!TryParseSettingFileHelper(args, ++i, parser: null)) - { - break; - } - } - } - } - /// /// Gets the word in a switch from the current argument or parses a file. /// For example -foo, /foo, or --foo would return 'foo'. @@ -566,35 +698,30 @@ internal static void EarlyParse(string[] args) /// /// The index in args to the argument to process. /// - /// - /// Used to parse files in the args. If not supplied, Files will not be parsed. - /// /// /// Used during parsing files. /// /// /// Returns a Tuple: - /// The first value is a String called SwitchKey with the word in a switch from the current argument or null. - /// The second value is a bool called ShouldBreak, indicating if the parsing look should break. + /// The first value is a String called 'switchKey' with the word in a switch from the current argument or null. + /// The second value is a bool called 'shouldBreak', indicating if the parsing look should break. /// - private static (string SwitchKey, bool ShouldBreak) GetSwitchKey(string[] args, ref int argIndex, CommandLineParameterParser parser, ref bool noexitSeen) + private (string switchKey, bool shouldBreak) GetSwitchKey(string[] args, ref int argIndex, ref bool noexitSeen) { - string switchKey = args[argIndex].Trim().ToLowerInvariant(); + string switchKey = args[argIndex].Trim(); if (string.IsNullOrEmpty(switchKey)) { - return (SwitchKey: null, ShouldBreak: false); + return (switchKey: string.Empty, shouldBreak: false); } - if (!CharExtensions.IsDash(switchKey[0]) && switchKey[0] != '/') + char firstChar = switchKey[0]; + if (!CharExtensions.IsDash(firstChar) && firstChar != '/') { - // then its a file - if (parser != null) - { - --argIndex; - parser.ParseFile(args, ref argIndex, noexitSeen); - } + // then it's a file + --argIndex; + ParseFile(args, ref argIndex, noexitSeen); - return (SwitchKey: null, ShouldBreak: true); + return (switchKey: string.Empty, shouldBreak: true); } // chop off the first character so that we're agnostic wrt specifying / or - @@ -602,71 +729,125 @@ private static (string SwitchKey, bool ShouldBreak) GetSwitchKey(string[] args, 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); } - return (SwitchKey: switchKey, ShouldBreak: false); + return (switchKey: switchKey, shouldBreak: false); } - private static string NormalizeFilePath(string path) + internal static string NormalizeFilePath(string path) { // Normalize slashes - path = path.Replace(StringLiterals.AlternatePathSeparator, - StringLiterals.DefaultPathSeparator); + path = path.Replace( + StringLiterals.AlternatePathSeparator, + StringLiterals.DefaultPathSeparator); 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(switchKey != null, "need a value"); Dbg.Assert(!string.IsNullOrEmpty(match), "need a value"); Dbg.Assert(match.Trim().ToLowerInvariant() == match, "match should be normalized to lowercase w/ no outside whitespace"); Dbg.Assert(smallestUnambiguousMatch.Trim().ToLowerInvariant() == smallestUnambiguousMatch, "match should be normalized to lowercase w/ no outside whitespace"); Dbg.Assert(match.Contains(smallestUnambiguousMatch), "sUM should be a substring of match"); - return (match.Trim().ToLowerInvariant().IndexOf(switchKey, StringComparison.Ordinal) == 0 && - switchKey.Length >= smallestUnambiguousMatch.Length); + return (switchKey.Length >= smallestUnambiguousMatch.Length + && match.StartsWith(switchKey, StringComparison.OrdinalIgnoreCase)); } #endregion - private void ShowHelp() + private void ShowError(PSHostUserInterface hostUI) { - Dbg.Assert(_helpText != null, "_helpText should not be null"); - _hostUI.WriteLine(string.Empty); - _hostUI.Write(_helpText); - if (_showExtendedHelp) + if (_error != null) { - _hostUI.Write(ManagedEntranceStrings.ExtendedHelp); + hostUI.WriteErrorLine(_error); } - - _hostUI.WriteLine(string.Empty); } - private void DisplayBanner() + private void ShowHelp(PSHostUserInterface hostUI, string? helpText) { - // If banner text is not supplied do nothing. - if (!string.IsNullOrEmpty(_bannerText)) + if (helpText is null) { - _hostUI.WriteLine(_bannerText); - _hostUI.WriteLine(); + return; + } + + if (_showHelp) + { + hostUI.WriteLine(); + hostUI.Write(helpText); + if (_showExtendedHelp) + { + hostUI.Write(ManagedEntranceStrings.ExtendedHelp); + } + + hostUI.WriteLine(); } } - internal bool StaMode + private void DisplayBanner(PSHostUserInterface hostUI, string? bannerText) { - get + if (_showBanner && !_showHelp) { - if (_staMode.HasValue) + // If banner text is not supplied do nothing. + if (!string.IsNullOrEmpty(bannerText)) { - return _staMode.Value; + hostUI.WriteLine(bannerText); } - else + + if (UpdatesNotification.CanNotifyUpdates) { - return true; + UpdatesNotification.ShowUpdateNotification(hostUI); } } } @@ -678,41 +859,43 @@ internal bool StaMode /// /// The command line parameters to be processed. /// - internal void Parse(string[] args) { - Dbg.Assert(!_dirty, "This instance has already been used. Create a new instance."); + if (_dirty) + { + throw new InvalidOperationException("This instance has already been used. Create a new instance."); + } - // indicates that we've called this method on this instance, and that when it's done, the state variables - // will reflect the parse. + for (int i = 0; i < args.Length; i++) + { + 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 + // will reflect the parse. _dirty = true; ParseHelper(args); - - // Check registry setting for a Group Policy ConfigurationName entry and - // use it to override anything set by the user. - var configurationName = GetConfigurationNameFromGroupPolicy(); - if (!string.IsNullOrEmpty(configurationName)) - { - _configurationName = configurationName; - } } private void ParseHelper(string[] args) { - Dbg.Assert(args != null, "Argument 'args' to ParseHelper should never be null"); + if (args.Length == 0) + { + return; + } + bool noexitSeen = false; for (int i = 0; i < args.Length; ++i) { - (string SwitchKey, bool ShouldBreak) switchKeyResults = GetSwitchKey(args, ref i, this, ref noexitSeen); - if (switchKeyResults.ShouldBreak) + (string switchKey, bool shouldBreak) switchKeyResults = GetSwitchKey(args, ref i, ref noexitSeen); + if (switchKeyResults.shouldBreak) { break; } - string switchKey = switchKeyResults.SwitchKey; + string switchKey = switchKeyResults.switchKey; // If version is in the commandline, don't continue to look at any other parameters if (MatchSwitch(switchKey, "version", "v")) @@ -722,6 +905,7 @@ private void ParseHelper(string[] args) _noInteractive = true; _skipUserInit = true; _noExit = false; + ParametersUsed |= ParameterBitmap.Version; break; } @@ -730,87 +914,142 @@ private void ParseHelper(string[] args) _showHelp = true; _showExtendedHelp = true; _abortStartup = true; + ParametersUsed |= ParameterBitmap.Help; } else if (MatchSwitch(switchKey, "login", "l")) { - // This handles -Login on Windows only, where it does nothing. - // On *nix, -Login is handled much earlier to improve startup performance. + // 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; + } +#if !UNIX + else if (MatchSwitch(switchKey, "v2socketservermode", "v2so")) + { + _v2SocketServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.V2SocketServerMode; } +#endif 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")) { ++i; if (i >= args.Length) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MissingConfigurationNameArgument); break; } _configurationName = args[i]; + ParametersUsed |= ParameterBitmap.ConfigurationName; } else if (MatchSwitch(switchKey, "custompipename", "cus")) { ++i; if (i >= args.Length) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MissingCustomPipeNameArgument); break; } - if (!Platform.IsWindows) +#if UNIX + int maxNameLength = MaxNameLength(); + if (args[i].Length > maxNameLength) { - int maxNameLength = (Platform.IsLinux ? MaxPipePathLengthLinux : MaxPipePathLengthMacOS) - Path.GetTempPath().Length; - if (args[i].Length > maxNameLength) - { - WriteCommandLineError( - string.Format( - CommandLineParameterParserStrings.CustomPipeNameTooLong, - maxNameLength, - args[i], - args[i].Length)); - break; - } + SetCommandLineError( + string.Format( + CommandLineParameterParserStrings.CustomPipeNameTooLong, + maxNameLength, + args[i], + args[i].Length)); + 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")) { @@ -818,34 +1057,36 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.Command; } else if (MatchSwitch(switchKey, "windowstyle", "w")) { #if UNIX - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.WindowStyleArgumentNotImplemented); break; #else ++i; if (i >= args.Length) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MissingWindowStyleArgument); break; } try { - ProcessWindowStyle style = (ProcessWindowStyle)LanguagePrimitives.ConvertTo( - args[i], typeof(ProcessWindowStyle), CultureInfo.InvariantCulture); - ConsoleControl.SetConsoleMode(style); + _windowStyle = LanguagePrimitives.ConvertTo(args[i]); } catch (PSInvalidCastException e) { - WriteCommandLineError( + SetCommandLineError( string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidWindowStyleArgument, args[i], e.Message)); break; } + + ParametersUsed |= ParameterBitmap.WindowStyle; #endif } else if (MatchSwitch(switchKey, "file", "f")) @@ -854,80 +1095,40 @@ private void ParseHelper(string[] args) { break; } - } -#if DEBUG - // this option is useful when debugging ConsoleHost remotely using VS remote debugging, as you can only - // attach to an already running process with that debugger. - else if (MatchSwitch(switchKey, "wait", "wa")) - { - // This does not need to be localized: its chk only - - ((ConsoleHostUserInterface)_hostUI).WriteToConsole("Waiting - type enter to continue:", false); - _hostUI.ReadLine(); - } - // this option is useful for testing the initial InitialSessionState experience - else if (MatchSwitch(switchKey, "iss", "iss")) - { - // Just toss this option, it was processed earlier... + ParametersUsed |= ParameterBitmap.File; } - - // this option is useful for testing the initial InitialSessionState experience - // this is independent of the normal wait switch because configuration processing - // happens so early in the cycle... +#if DEBUG else if (MatchSwitch(switchKey, "isswait", "isswait")) { - // Just toss this option, it was processed earlier... - } - else if (MatchSwitch(switchKey, "modules", "mod")) - { - if (ConsoleHost.DefaultInitialSessionState == null) - { - WriteCommandLineError( - "The -module option can only be specified with the -iss option.", - showHelp: true, - showBanner: false); - break; - } - - ++i; - int moduleCount = 0; - // Accumulate the arguments to this script... - while (i < args.Length) - { - string arg = args[i]; - - if (!string.IsNullOrEmpty(arg) && CharExtensions.IsDash(arg[0])) - { - break; - } - else - { - ConsoleHost.DefaultInitialSessionState.ImportPSModule(new string[] { arg }); - moduleCount++; - } - - ++i; - } - - if (moduleCount < 1) - { - _hostUI.WriteErrorLine("No modules specified for -module option"); - } + // Just toss this option, it was processed earlier in 'ManagedEntrance.Start()'. } #endif else if (MatchSwitch(switchKey, "outputformat", "o") || MatchSwitch(switchKey, "of", "o")) { ParseFormat(args, ref i, ref _outFormat, CommandLineParameterParserStrings.MissingOutputFormatParameter); _outputFormatSpecified = true; + ParametersUsed |= ParameterBitmap.OutputFormat; } - else if (MatchSwitch(switchKey, "inputformat", "in") || MatchSwitch(switchKey, "if", "if")) + 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")) { @@ -936,6 +1137,8 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.EncodedCommand; } else if (MatchSwitch(switchKey, "encodedarguments", "encodeda") || MatchSwitch(switchKey, "ea", "ea")) { @@ -943,20 +1146,24 @@ private void ParseHelper(string[] args) { break; } + + ParametersUsed |= ParameterBitmap.EncodedArgument; } else if (MatchSwitch(switchKey, "settingsfile", "settings")) { // Parse setting file arg and write error - if (!TryParseSettingFileHelper(args, ++i, this)) + if (!TryParseSettingFileHelper(args, ++i)) { break; } + + ParametersUsed |= ParameterBitmap.SettingsFile; } - else if (MatchSwitch(switchKey, "sta", "s")) + else if (MatchSwitch(switchKey, "sta", "sta")) { - if (!Platform.IsWindowsDesktop) + if (!Platform.IsWindowsDesktop || !Platform.IsStaSupported) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.STANotImplemented); break; } @@ -964,18 +1171,19 @@ private void ParseHelper(string[] args) if (_staMode.HasValue) { // -sta and -mta are mutually exclusive. - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MtaStaMutuallyExclusive); break; } _staMode = true; + ParametersUsed |= ParameterBitmap.STA; } else if (MatchSwitch(switchKey, "mta", "mta")) { if (!Platform.IsWindowsDesktop) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MTANotImplemented); break; } @@ -983,55 +1191,75 @@ private void ParseHelper(string[] args) if (_staMode.HasValue) { // -sta and -mta are mutually exclusive. - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MtaStaMutuallyExclusive); break; } _staMode = false; + ParametersUsed |= ParameterBitmap.MTA; } else if (MatchSwitch(switchKey, "workingdirectory", "wo") || MatchSwitch(switchKey, "wd", "wd")) { ++i; if (i >= args.Length) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MissingWorkingDirectoryArgument); break; } _workingDirectory = args[i]; + ParametersUsed |= ParameterBitmap.WorkingDirectory; } #if !UNIX else if (MatchSwitch(switchKey, "removeworkingdirectorytrailingcharacter", "removeworkingdirectorytrailingcharacter")) { _removeWorkingDirectoryTrailingCharacter = true; } + else if (MatchSwitch(switchKey, "token", "to")) + { + ++i; + if (i >= args.Length) + { + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-Token")); + break; + } + + _token = args[i]; + + // Not adding anything to ParametersUsed, because it is required with V2 socket server mode + // So, we can assume it based on that bit + } + else if (MatchSwitch(switchKey, "utctimestamp", "utc")) + { + ++i; + if (i >= args.Length) + { + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-UTCTimestamp")); + break; + } + + // Parse as iso8601UtcString + _utcTimestamp = DateTimeOffset.ParseExact(args[i], "yyyy-MM-dd'T'HH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + + // Not adding anything to ParametersUsed, because it is required with V2 socket server mode + // So, we can assume it based on that bit + } #endif else { // The first parameter we fail to recognize marks the beginning of the file string. - --i; if (!ParseFile(args, ref i, noexitSeen)) { break; } - } - } - - if (_showHelp) - { - ShowHelp(); - } - if (_showBanner && !_showHelp) - { - DisplayBanner(); - - if (UpdatesNotification.CanNotifyUpdates) - { - UpdatesNotification.ShowUpdateNotification(_hostUI); + // default to filename being the next argument. + ParametersUsed |= ParameterBitmap.File; } } @@ -1041,9 +1269,21 @@ private void ParseHelper(string[] args) "if exit code is failure, then abortstartup should be true"); } - private void WriteCommandLineError(string msg, bool showHelp = false, bool showBanner = false) + internal void ShowErrorHelpBanner(PSHostUserInterface hostUI, string? bannerText, string? helpText) { - _hostUI.WriteErrorLine(msg); + ShowError(hostUI); + ShowHelp(hostUI, helpText); + DisplayBanner(hostUI, bannerText); + } + + private void SetCommandLineError(string msg, bool showHelp = false, bool showBanner = false) + { + if (_error != null) + { + throw new ArgumentException(nameof(SetCommandLineError), nameof(_error)); + } + + _error = msg; _showHelp = showHelp; _showBanner = showBanner; _abortStartup = true; @@ -1053,7 +1293,7 @@ private void WriteCommandLineError(string msg, bool showHelp = false, bool showB 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); @@ -1062,13 +1302,11 @@ private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat ++i; if (i >= args.Length) { - _hostUI.WriteErrorLine( + SetCommandLineError( StringUtil.Format( resourceStr, - sb.ToString())); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + sb.ToString()), + showHelp: true); return; } @@ -1078,61 +1316,37 @@ private void ParseFormat(string[] args, ref int i, ref Serialization.DataFormat } catch (ArgumentException) { - _hostUI.WriteErrorLine( + SetCommandLineError( StringUtil.Format( CommandLineParameterParserStrings.BadFormatParameterValue, args[i], - sb.ToString())); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + sb.ToString()), + showHelp: true); } } - private void ParseExecutionPolicy(string[] args, ref int i, ref string executionPolicy, string resourceStr) + private void ParseExecutionPolicy(string[] args, ref int i, ref string? executionPolicy, string resourceStr) { ++i; if (i >= args.Length) { - _hostUI.WriteErrorLine(resourceStr); - - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(resourceStr, showHelp: true); return; } executionPolicy = args[i]; } + // Process file execution. We don't need to worry about checking -command + // since if -command comes before -file, -file will be treated as part + // of the script to evaluate. If -file comes before -command, it will + // treat -command as an argument to the script... private bool ParseFile(string[] args, ref int i, bool noexitSeen) { - // Process file execution. We don't need to worry about checking -command - // since if -command comes before -file, -file will be treated as part - // of the script to evaluate. If -file comes before -command, it will - // treat -command as an argument to the script... - - bool TryGetBoolValue(string arg, out bool boolValue) - { - if (arg.Equals("$true", StringComparison.OrdinalIgnoreCase) || arg.Equals("true", StringComparison.OrdinalIgnoreCase)) - { - boolValue = true; - return true; - } - else if (arg.Equals("$false", StringComparison.OrdinalIgnoreCase) || arg.Equals("false", StringComparison.OrdinalIgnoreCase)) - { - boolValue = false; - return true; - } - - boolValue = false; - return false; - } - ++i; if (i >= args.Length) { - WriteCommandLineError( + SetCommandLineError( CommandLineParameterParserStrings.MissingFileArgument, showHelp: true, showBanner: false); @@ -1160,7 +1374,6 @@ bool TryGetBoolValue(string arg, out bool boolValue) // We need to get the full path to the script because it will be // executed after the profiles are run and they may change the current // directory. - string exceptionMessage = null; try { _file = NormalizeFilePath(args[i]); @@ -1169,13 +1382,8 @@ bool TryGetBoolValue(string arg, out bool boolValue) { // Catch all exceptions - we're just going to exit anyway so there's // no issue of the system being destabilized. - exceptionMessage = e.Message; - } - - if (exceptionMessage != null) - { - WriteCommandLineError( - string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgument, args[i], exceptionMessage), + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidFileArgument, args[i], e.Message), showBanner: false); return false; } @@ -1184,11 +1392,11 @@ bool TryGetBoolValue(string arg, out bool boolValue) { if (args[i].StartsWith('-') && args[i].Length > 1) { - string param = args[i].Substring(1, args[i].Length - 1).ToLower(); + string param = args[i].Substring(1, args[i].Length - 1); StringBuilder possibleParameters = new StringBuilder(); - foreach (string validParameter in validParameters) + foreach (string validParameter in s_validParameters) { - if (validParameter.Contains(param)) + if (validParameter.Contains(param, StringComparison.OrdinalIgnoreCase)) { possibleParameters.Append("\n -"); possibleParameters.Append(validParameter); @@ -1197,74 +1405,89 @@ bool TryGetBoolValue(string arg, out bool boolValue) if (possibleParameters.Length > 0) { - WriteCommandLineError( - string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidArgument, args[i]), + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.InvalidArgument, args[i]) + + Environment.NewLine + + possibleParameters.ToString(), showBanner: false); - WriteCommandLineError(possibleParameters.ToString(), showBanner: false); return false; } } - WriteCommandLineError( + SetCommandLineError( string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.ArgumentFileDoesNotExist, args[i]), 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); - if (TryGetBoolValue(argValue, out bool boolValue)) - { - _collectedArgs.Add(new CommandParameter(argName, boolValue)); - } - else - { - _collectedArgs.Add(new CommandParameter(argName, 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) @@ -1272,21 +1495,14 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco if (_commandLineCommand != null) { // we've already set the command, so squawk - - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.CommandAlreadySpecified); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.CommandAlreadySpecified, showHelp: true); return false; } ++i; if (i >= args.Length) { - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingCommandParameter); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.MissingCommandParameter, showHelp: true); return false; } @@ -1299,10 +1515,7 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco // decoding failed catch { - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadCommandValue); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.BadCommandValue, showHelp: true); return false; } } @@ -1318,41 +1531,27 @@ private bool ParseCommand(string[] args, ref int i, bool noexitSeen, bool isEnco { // there are more parameters to -command than -, which is an error. - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.TooManyParametersToCommand); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.TooManyParametersToCommand, showHelp: true); return false; } - if (!Console.IsInputRedirected) + if (InputRedirectedTestHook.HasValue ? !InputRedirectedTestHook.Value : !Console.IsInputRedirected) { - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.StdinNotRedirected); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.StdinNotRedirected, showHelp: true); return false; } } 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) @@ -1370,20 +1569,14 @@ private bool CollectArgs(string[] args, ref int i) { if (_collectedArgs.Count != 0) { - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.ArgsAlreadySpecified); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.ArgsAlreadySpecified, showHelp: true); return false; } ++i; if (i >= args.Length) { - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.MissingArgsValue); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.MissingArgsValue, showHelp: true); return false; } @@ -1401,11 +1594,7 @@ private bool CollectArgs(string[] args, ref int i) catch { // decoding failed - - _hostUI.WriteErrorLine(CommandLineParameterParserStrings.BadArgsValue); - _showHelp = true; - _abortStartup = true; - _exitCode = ConsoleHost.ExitCodeBadCommandLineParameter; + SetCommandLineError(CommandLineParameterParserStrings.BadArgsValue, showHelp: true); return false; } @@ -1413,40 +1602,49 @@ private bool CollectArgs(string[] args, ref int i) } private bool _socketServerMode; +#if !UNIX + private bool _v2SocketServerMode; +#endif private bool _serverMode; private bool _namedPipeServerMode; private bool _sshServerMode; + private bool _noProfileLoadTime; private bool _showVersion; - private string _configurationName; - private PSHostUserInterface _hostUI; + private string? _configurationFile; + private string? _configurationName; + private string? _error; private bool _showHelp; private bool _showExtendedHelp; private bool _showBanner = true; private bool _noInteractive; - private string _bannerText; - private string _helpText; private bool _abortStartup; private bool _skipUserInit; - private string _customPipeName; + private string? _customPipeName; private bool? _staMode = null; private bool _noExit = true; private bool _explicitReadCommandsFromStdin; private bool _noPrompt; - private string _commandLineCommand; + private string? _commandLineCommand; private bool _wasCommandEncoded; + private bool _commandHasArgs; private uint _exitCode = ConsoleHost.ExitCodeSuccess; private bool _dirty; private Serialization.DataFormat _outFormat = Serialization.DataFormat.Text; private bool _outputFormatSpecified = false; private Serialization.DataFormat _inFormat = Serialization.DataFormat.Text; - private Collection _collectedArgs = new Collection(); - private string _file; - private string _executionPolicy; - private string _workingDirectory; + private readonly Collection _collectedArgs = new Collection(); + private string? _file; + private string? _executionPolicy; + private string? _settingsFile; + private string? _workingDirectory; +#if !UNIX + private string? _token; + private DateTimeOffset? _utcTimestamp; +#endif #if !UNIX + private ProcessWindowStyle? _windowStyle; private bool _removeWorkingDirectoryTrailingCharacter = false; #endif } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs index 6159e349497..7bda4bc5688 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; @@ -44,7 +44,6 @@ namespace Microsoft.PowerShell /// Class ConsoleControl is used to wrap the various win32 console APIs 1:1 (i.e. at a low level, without attempting to be a /// "true" object-oriented library. /// - internal static class ConsoleControl { #if !UNIX @@ -110,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}"); } } @@ -162,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}"); } } @@ -193,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}"); } } @@ -212,41 +211,6 @@ internal struct FONTSIGNATURE internal DWORD fsCsb1; } - [StructLayout(LayoutKind.Sequential)] - internal struct CHARSETINFO - { - // From public\sdk\inc\wingdi.h - internal uint ciCharset; // Character set value. - internal uint ciACP; // ANSI code-page identifier. - internal FONTSIGNATURE fs; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct TEXTMETRIC - { - // From public\sdk\inc\wingdi.h - public int tmHeight; - public int tmAscent; - public int tmDescent; - public int tmInternalLeading; - public int tmExternalLeading; - public int tmAveCharWidth; - public int tmMaxCharWidth; - public int tmWeight; - public int tmOverhang; - public int tmDigitizedAspectX; - public int tmDigitizedAspectY; - public char tmFirstChar; - public char tmLastChar; - public char tmDefaultChar; - public char tmBreakChar; - public byte tmItalic; - public byte tmUnderlined; - public byte tmStruckOut; - public byte tmPitchAndFamily; - public byte tmCharSet; - } - #region SentInput Data Structures [StructLayout(LayoutKind.Sequential)] @@ -276,13 +240,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; @@ -448,10 +412,10 @@ internal enum KeyboardFlag : uint /// /// The window to show... /// The command to do. - /// True it it was successful. + /// 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) { @@ -481,7 +445,6 @@ internal static void SetConsoleMode(ProcessWindowStyle style) /// /// Types of control ConsoleBreakSignals received by break Win32Handler delegates. /// - internal enum ConsoleBreakSignal : uint { // These correspond to the CRTL_XXX_EVENT #defines in public/sdk/inc/wincon.h @@ -511,12 +474,11 @@ internal enum ConsoleBreakSignal : uint /// /// If Win32's SetConsoleCtrlHandler fails /// - internal static void AddBreakHandler(BreakHandler handlerDelegate) { bool result = NativeMethods.SetConsoleCtrlHandler(handlerDelegate, true); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -532,12 +494,11 @@ internal static void AddBreakHandler(BreakHandler handlerDelegate) /// /// If Win32's SetConsoleCtrlHandler fails /// - internal static void RemoveBreakHandler() { bool result = NativeMethods.SetConsoleCtrlHandler(null, false); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -555,8 +516,7 @@ internal static void RemoveBreakHandler() { var handle = NativeMethods.CreateFile( "CONIN$", - (UInt32) - (NativeMethods.AccessQualifiers.GenericRead | NativeMethods.AccessQualifiers.GenericWrite), + (UInt32)(NativeMethods.AccessQualifiers.GenericRead | NativeMethods.AccessQualifiers.GenericWrite), (UInt32)NativeMethods.ShareModes.ShareRead, (IntPtr)0, (UInt32)NativeMethods.CreationDisposition.OpenExisting, @@ -657,7 +617,6 @@ internal enum ConsoleModes : uint /// /// If Win32's GetConsoleMode fails /// - internal static ConsoleModes GetMode(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid"); @@ -666,7 +625,7 @@ internal static ConsoleModes GetMode(ConsoleHandle consoleHandle) UInt32 m = 0; bool result = NativeMethods.GetConsoleMode(consoleHandle.DangerousGetHandle(), out m); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -690,7 +649,6 @@ internal static ConsoleModes GetMode(ConsoleHandle consoleHandle) /// /// If Win32's SetConsoleMode fails /// - internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) { Dbg.Assert(!consoleHandle.IsInvalid, "consoleHandle is not valid"); @@ -698,7 +656,7 @@ internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) bool result = NativeMethods.SetConsoleMode(consoleHandle.DangerousGetHandle(), (DWORD)mode); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -740,7 +698,6 @@ internal static void SetMode(ConsoleHandle consoleHandle, ConsoleModes mode) /// /// If Win32's ReadConsole fails /// - internal static string ReadConsole( ConsoleHandle consoleHandle, int initialContentLength, @@ -777,7 +734,7 @@ internal static string ReadConsole( out charsReaded, ref control); keyState = control.dwControlKeyState; - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -813,7 +770,6 @@ internal static string ReadConsole( /// /// If Win32's ReadConsoleInput fails /// - internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECORD[] buffer) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -826,7 +782,7 @@ internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECO buffer, (DWORD)buffer.Length, out recordsRead); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -853,7 +809,6 @@ internal static int ReadConsoleInput(ConsoleHandle consoleHandle, ref INPUT_RECO /// /// If Win32's PeekConsoleInput fails /// - internal static int PeekConsoleInput ( ConsoleHandle consoleHandle, @@ -871,7 +826,7 @@ ref INPUT_RECORD[] buffer (DWORD)buffer.Length, out recordsRead); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -895,7 +850,6 @@ ref INPUT_RECORD[] buffer /// /// If Win32's GetNumberOfConsoleInputEvents fails /// - internal static int GetNumberOfConsoleInputEvents(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -904,7 +858,7 @@ internal static int GetNumberOfConsoleInputEvents(ConsoleHandle consoleHandle) DWORD numEvents; bool result = NativeMethods.GetNumberOfConsoleInputEvents(consoleHandle.DangerousGetHandle(), out numEvents); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -934,7 +888,7 @@ internal static void FlushConsoleInputBuffer(ConsoleHandle consoleHandle) NakedWin32Handle h = consoleHandle.DangerousGetHandle(); result = NativeMethods.FlushConsoleInputBuffer(h); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -969,7 +923,7 @@ internal static CONSOLE_SCREEN_BUFFER_INFO GetConsoleScreenBufferInfo(ConsoleHan CONSOLE_SCREEN_BUFFER_INFO bufferInfo; bool result = NativeMethods.GetConsoleScreenBufferInfo(consoleHandle.DangerousGetHandle(), out bufferInfo); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -989,7 +943,6 @@ internal static CONSOLE_SCREEN_BUFFER_INFO GetConsoleScreenBufferInfo(ConsoleHan /// /// If Win32's SetConsoleScreenBufferSize fails /// - internal static void SetConsoleScreenBufferSize(ConsoleHandle consoleHandle, Size newSize) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -1002,7 +955,7 @@ internal static void SetConsoleScreenBufferSize(ConsoleHandle consoleHandle, Siz bool result = NativeMethods.SetConsoleScreenBufferSize(consoleHandle.DangerousGetHandle(), s); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -1078,7 +1031,6 @@ internal static WORD ColorToWORD(ConsoleColor foreground, ConsoleColor backgroun /// /// If it is illegal to write to the output buffer /// - internal static void WriteConsoleOutput(ConsoleHandle consoleHandle, Coordinates origin, BufferCell[,] contents) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -1262,8 +1214,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 @@ -1278,8 +1229,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}]")); } } } @@ -1288,8 +1238,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 @@ -1304,8 +1253,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}]")); } } } @@ -1325,7 +1273,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) @@ -1340,7 +1288,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}]")); } } } @@ -1492,9 +1440,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, @@ -1504,9 +1450,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, @@ -1515,7 +1458,7 @@ private static void WriteConsoleOutputCJK(ConsoleHandle consoleHandle, Coordinat ref writeRegion); } - if (result == false) + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -1540,7 +1483,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; } @@ -1643,7 +1586,7 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin bufferCoord, ref writeRegion); - if (result == false) + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -1668,7 +1611,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; } @@ -1705,7 +1648,6 @@ private static void WriteConsoleOutputPlain(ConsoleHandle consoleHandle, Coordin /// /// If there is not enough memory to complete calls to Win32's ReadConsoleOutput /// - internal static void ReadConsoleOutput ( ConsoleHandle consoleHandle, @@ -1746,10 +1688,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); @@ -1813,9 +1752,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, @@ -1972,7 +1909,7 @@ internal static void ReadConsoleOutputCJK new Coordinates(readRegion.Left, readRegion.Top), atContents, ref contents); - if (result == false) + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -1999,7 +1936,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; } @@ -2144,7 +2081,7 @@ private static void ReadConsoleOutputPlain bufferCoord, ref readRegion); - if (result == false) + if (!result) { // When WriteConsoleOutput fails, half bufferLimit if (bufferLimit < 2) @@ -2169,7 +2106,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; } @@ -2184,8 +2121,7 @@ private static void ReadConsoleOutputPlain { for (int c = atContentsCol; c < bufferSize.X + atContentsCol; c++, characterBufferIndex++) { - contents[r, c].Character = (char) - characterBuffer[characterBufferIndex].UnicodeChar; + contents[r, c].Character = (char)characterBuffer[characterBufferIndex].UnicodeChar; ConsoleColor fgColor, bgColor; WORDToColor(characterBuffer[characterBufferIndex].Attributes, out fgColor, @@ -2285,15 +2221,14 @@ 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); - if (result == false) + out _); + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2339,16 +2274,15 @@ 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 == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2379,7 +2313,6 @@ Coordinates origin /// /// If Win32's ScrollConsoleScreenBuffer fails /// - internal static void ScrollConsoleScreenBuffer ( ConsoleHandle consoleHandle, @@ -2399,7 +2332,7 @@ internal static void ScrollConsoleScreenBuffer destOrigin, ref fill); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2430,7 +2363,6 @@ internal static void ScrollConsoleScreenBuffer /// /// If Win32's SetConsoleWindowInfo fails /// - internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool absolute, SMALL_RECT windowInfo) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2438,7 +2370,7 @@ internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool abso bool result = NativeMethods.SetConsoleWindowInfo(consoleHandle.DangerousGetHandle(), absolute, ref windowInfo); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2460,7 +2392,6 @@ internal static void SetConsoleWindowInfo(ConsoleHandle consoleHandle, bool abso /// /// If Win32's GetLargestConsoleWindowSize fails /// - internal static Size GetLargestConsoleWindowSize(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2490,17 +2421,13 @@ internal static Size GetLargestConsoleWindowSize(ConsoleHandle consoleHandle) /// /// If Win32's GetConsoleTitle fails /// - internal static string GetConsoleWindowTitle() { const int MaxWindowTitleLength = 1024; - DWORD bufferSize = MaxWindowTitleLength; + const DWORD bufferSize = MaxWindowTitleLength; 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 @@ -2513,6 +2440,8 @@ internal static string GetConsoleWindowTitle() return consoleTitle.ToString(); } + private static bool s_dontsetConsoleWindowTitle; + /// /// Wraps Win32 SetConsoleTitle. /// @@ -2522,15 +2451,29 @@ internal static string GetConsoleWindowTitle() /// /// If Win32's SetConsoleTitle fails /// - internal static void SetConsoleWindowTitle(string consoleTitle) { + if (s_dontsetConsoleWindowTitle) + { + return; + } + bool result = NativeMethods.SetConsoleTitle(consoleTitle); - if (result == false) + 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; @@ -2569,40 +2512,54 @@ internal static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan outBuffer; - while (cursor < output.Length) + // 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) { - ReadOnlySpan outBuffer; + outBuffer = output.Slice(cursor, MaxBufferSize); + cursor += MaxBufferSize; + WriteConsole(consoleHandle, outBuffer); + } - if (cursor + MaxBufferSize < output.Length) - { - outBuffer = output.Slice(cursor, MaxBufferSize); - cursor += MaxBufferSize; + outBuffer = output.Slice(cursor); + if (!newLine) + { + WriteConsole(consoleHandle, outBuffer); + return; + } - WriteConsole(consoleHandle, outBuffer); - } - else + char[] rentedArray = null; + string lineEnding = Environment.NewLine; + int size = outBuffer.Length + lineEnding.Length; + + // 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; - Span outBufferLine = stackalloc char[outBuffer.Length + endOfLineLength]; - 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); } } } @@ -2618,7 +2575,7 @@ private static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan out charsWritten, IntPtr.Zero); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2643,7 +2600,6 @@ private static void WriteConsole(ConsoleHandle consoleHandle, ReadOnlySpan /// /// if the Win32's SetConsoleTextAttribute fails /// - internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD attribute) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2651,7 +2607,7 @@ internal static void SetConsoleTextAttribute(ConsoleHandle consoleHandle, WORD a bool result = NativeMethods.SetConsoleTextAttribute(consoleHandle.DangerousGetHandle(), attribute); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2673,7 +2629,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 @@ -2718,7 +2674,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: @@ -2808,7 +2764,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); @@ -2837,41 +2793,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 == false) - { - int err = Marshal.GetLastWin32Error(); - - HostException e = CreateHostException(err, "SetConsoleCursorPosition", - ErrorCategory.ResourceUnavailable, ConsoleControlStrings.SetConsoleCursorPositionExceptionTemplate); - throw e; - } - } - /// /// Wraps Win32 GetConsoleCursorInfo. /// @@ -2884,7 +2805,6 @@ internal static void SetConsoleCursorPosition(ConsoleHandle consoleHandle, Coord /// /// If Win32's GetConsoleCursorInfo fails /// - internal static CONSOLE_CURSOR_INFO GetConsoleCursorInfo(ConsoleHandle consoleHandle) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2894,7 +2814,7 @@ internal static CONSOLE_CURSOR_INFO GetConsoleCursorInfo(ConsoleHandle consoleHa bool result = NativeMethods.GetConsoleCursorInfo(consoleHandle.DangerousGetHandle(), out cursorInfo); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2915,7 +2835,7 @@ internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHan fontInfo.cbSize = Marshal.SizeOf(fontInfo); bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -2939,7 +2859,6 @@ internal static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHan /// /// If Win32's SetConsoleCursorInfo fails /// - internal static void SetConsoleCursorInfo(ConsoleHandle consoleHandle, CONSOLE_CURSOR_INFO cursorInfo) { Dbg.Assert(!consoleHandle.IsInvalid, "ConsoleHandle is not valid"); @@ -2947,7 +2866,7 @@ internal static void SetConsoleCursorInfo(ConsoleHandle consoleHandle, CONSOLE_C bool result = NativeMethods.SetConsoleCursorInfo(consoleHandle.DangerousGetHandle(), ref cursorInfo); - if (result == false) + if (!result) { int err = Marshal.GetLastWin32Error(); @@ -3002,7 +2921,6 @@ internal static void MimicKeyPress(INPUT[] inputs) /// /// Class to hold the Native Methods used in this file enclosing class. /// - internal static class NativeMethods { internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // WinBase.h @@ -3182,10 +3100,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); @@ -3236,7 +3150,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)] @@ -3269,7 +3183,7 @@ internal enum CHAR_INFO_Attributes : uint } [TraceSourceAttribute("ConsoleControl", "Console control methods")] - private static PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleControl", "Console control methods"); + private static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleControl", "Console control methods"); #endif } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index 39a32d77c22..daf69d50251 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -#pragma warning disable 1634, 1691 - using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -12,27 +9,29 @@ using System.Globalization; using System.IO; using System.Management.Automation; +using System.Management.Automation.Configuration; using System.Management.Automation.Host; 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; using System.Runtime.InteropServices; using System.Text; using System.Threading; -using Microsoft.PowerShell.Telemetry; using Microsoft.PowerShell.Commands; - -using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; -using Dbg = System.Management.Automation.Diagnostics; -using Debugger = System.Management.Automation.Debugger; - +using Microsoft.PowerShell.Telemetry; #if LEGACYTELEMETRY using Microsoft.PowerShell.Telemetry.Internal; #endif +using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; +using Dbg = System.Management.Automation.Diagnostics; +using Debugger = System.Management.Automation.Debugger; namespace Microsoft.PowerShell { @@ -55,11 +54,10 @@ internal sealed partial class ConsoleHost internal const int ExitCodeCtrlBreak = 128 + 21; // SIGBREAK internal const int ExitCodeInitFailure = 70; // Internal Software Error internal const int ExitCodeBadCommandLineParameter = 64; // Command Line Usage Error - private const uint SPI_GETSCREENREADER = 0x0046; - - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref bool pvParam, uint fWinIni); +#if UNIX + internal const string DECCKM_ON = "\x1b[?1h"; + internal const string DECCKM_OFF = "\x1b[?1l"; +#endif /// /// Internal Entry point in msh console host implementation. @@ -70,8 +68,8 @@ internal sealed partial class ConsoleHost /// /// Help text for minishell. This is displayed on 'minishell -?'. /// - /// - /// Command line parameters to pwsh.exe + /// + /// True when an external caller provides an InitialSessionState object, which can conflict with '-ConfigurationFile' argument. /// /// /// The exit code for the shell. @@ -99,7 +97,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, string[] args) + internal static int Start( + string bannerText, + string helpText, + bool issProvidedExternally) { #if DEBUG if (Environment.GetEnvironmentVariable("POWERSHELL_DEBUG_STARTUP") != null) @@ -111,17 +112,35 @@ internal static int Start(string bannerText, string helpText, string[] args) } #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]; + } + + pshome += Path.PathSeparator; - // to not impact startup perf, we don't remove duplicates, but we avoid adding a duplicate to the front + // 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)) { Environment.SetEnvironmentVariable("PATH", pshome); } - else if (!path.StartsWith(pshome)) + else if (!path.StartsWith(pshome, StringComparison.Ordinal)) { Environment.SetEnvironmentVariable("PATH", pshome + path); } @@ -129,13 +148,16 @@ internal static int Start(string bannerText, string helpText, string[] args) try { string profileDir = Platform.CacheDirectory; -#if ! UNIX - if (!Directory.Exists(profileDir)) + if (!string.IsNullOrEmpty(profileDir)) { - Directory.CreateDirectory(profileDir); - } +#if !UNIX + if (!Directory.Exists(profileDir)) + { + Directory.CreateDirectory(profileDir); + } #endif - ProfileOptimization.SetProfileRoot(profileDir); + ProfileOptimization.SetProfileRoot(profileDir); + } } catch { @@ -161,33 +183,42 @@ internal static int Start(string bannerText, string helpText, string[] args) hostException = e; } - PSHostUserInterface hostUi = s_theConsoleHost?.UI ?? new NullHostUserInterface(); - s_cpp = new CommandLineParameterParser(hostUi, bannerText, helpText); - s_cpp.Parse(args); - -#if UNIX - // On Unix, logging has to be deferred until after command-line parsing - // completes to allow overriding logging options. - PSEtwLog.LogConsoleStartup(); -#endif + PSHostUserInterface hostUI = s_theConsoleHost?.UI ?? new NullHostUserInterface(); + s_cpp.ShowErrorHelpBanner(hostUI, bannerText, helpText); if (s_cpp.ShowVersion) { // 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; } // Servermode parameter validation check. - if ((s_cpp.ServerMode && s_cpp.NamedPipeServerMode) || (s_cpp.ServerMode && s_cpp.SocketServerMode) || (s_cpp.NamedPipeServerMode && s_cpp.SocketServerMode)) + int serverModeCount = 0; + if (s_cpp.ServerMode) + { + serverModeCount++; + } + if (s_cpp.NamedPipeServerMode) + { + serverModeCount++; + } + if (s_cpp.SocketServerMode) + { + serverModeCount++; + } +#if !UNIX + if (s_cpp.V2SocketServerMode) + { + serverModeCount++; + } +#endif + if (serverModeCount > 1) { s_tracer.TraceError("Conflicting server mode parameters, parameters must be used exclusively."); - if (s_theConsoleHost != null) - { - s_theConsoleHost.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters); - } + s_theConsoleHost?.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters); return ExitCodeBadCommandLineParameter; } @@ -195,36 +226,74 @@ internal static int Start(string bannerText, string helpText, string[] args) #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.SSHServerMode) + { + 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.NamedPipeServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe"); + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("NamedPipe", s_cpp.ParametersUsedAsDouble); ProfileOptimization.StartProfile("StartupProfileData-NamedPipeServerMode"); - System.Management.Automation.Remoting.RemoteSessionNamedPipeServer.RunServerMode( - s_cpp.ConfigurationName); + RemoteSessionNamedPipeServer.RunServerMode( + configurationName: s_cpp.ConfigurationName); exitCode = 0; } - else if (s_cpp.SSHServerMode) +#if !UNIX + else if (s_cpp.V2SocketServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SSHServer"); - ProfileOptimization.StartProfile("StartupProfileData-SSHServerMode"); - System.Management.Automation.Remoting.Server.SSHProcessMediator.Run(s_cpp.InitialCommand); + if (s_cpp.Token == null) + { + s_tracer.TraceError("Token is required for V2SocketServerMode."); + s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-Token", "-V2SocketServerMode")); + return ExitCodeBadCommandLineParameter; + } + + if (s_cpp.UTCTimestamp == null) + { + s_tracer.TraceError("UTCTimestamp is required for V2SocketServerMode."); + s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-UTCTimestamp", "-v2socketservermode")); + return ExitCodeBadCommandLineParameter; + } + + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("V2SocketServerMode", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-V2SocketServerMode"); + HyperVSocketMediator.Run( + initialCommand: s_cpp.InitialCommand, + configurationName: s_cpp.ConfigurationName, + token: s_cpp.Token, + tokenCreationTime: s_cpp.UTCTimestamp.Value); + exitCode = 0; } +#endif else if (s_cpp.SocketServerMode) { - ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode"); + 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 @@ -236,7 +305,7 @@ internal static int Start(string bannerText, string helpText, string[] args) throw hostException; } - if (s_theConsoleHost.LoadPSReadline()) + if (LoadPSReadline()) { ProfileOptimization.StartProfile("StartupProfileData-Interactive"); @@ -252,23 +321,33 @@ internal static int Start(string bannerText, string helpText, string[] args) } s_theConsoleHost.BindBreakHandler(); - PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; + 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 @@ -277,7 +356,29 @@ internal static int Start(string bannerText, string helpText, string[] args) } } - private static CommandLineParameterParser s_cpp; + internal static void ParseCommandLine(string[] args) + { + s_cpp.Parse(args); + +#if !UNIX + if (s_cpp.WindowStyle.HasValue) + { + ConsoleControl.SetConsoleMode(s_cpp.WindowStyle.Value); + } +#endif + + if (s_cpp.SettingsFile is not null) + { + 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 on the command line. + // It depends on setting file so 'SetSystemConfigFilePath()' should be called before. + s_cpp.ConfigurationName = CommandLineParameterParser.GetConfigurationNameFromGroupPolicy(); + } + + private static readonly CommandLineParameterParser s_cpp = new CommandLineParameterParser(); #if UNIX /// @@ -388,8 +489,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; @@ -404,7 +504,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. @@ -455,10 +555,7 @@ private static void HandleBreak() if (runspaceRef != null) { var runspace = runspaceRef.Runspace; - if (runspace != null) - { - runspace.Close(); - } + runspace?.Close(); } } } @@ -521,7 +618,6 @@ internal static ConsoleHost SingletonInstance /// /// /// - public override string Name { get @@ -538,7 +634,6 @@ public override string Name /// /// /// - public override System.Version Version { get @@ -553,7 +648,6 @@ public override System.Version Version /// /// /// - public override System.Guid InstanceId { get; } = Guid.NewGuid(); /// @@ -573,12 +667,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. @@ -708,7 +808,10 @@ public Runspace Runspace { get { - if (this.RunspaceRef == null) { return null; } + if (this.RunspaceRef == null) + { + return null; + } return this.RunspaceRef.Runspace; } @@ -723,7 +826,10 @@ internal LocalRunspace LocalRunspace return RunspaceRef.OldRunspace as LocalRunspace; } - if (RunspaceRef == null) { return null; } + if (RunspaceRef == null) + { + return null; + } return RunspaceRef.Runspace as LocalRunspace; } @@ -731,11 +837,11 @@ internal LocalRunspace LocalRunspace public class ConsoleColorProxy { - private ConsoleHostUserInterface _ui; + private readonly ConsoleHostUserInterface _ui; public ConsoleColorProxy(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException(nameof(ui)); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; } @@ -928,8 +1034,12 @@ public override PSObject PrivateData { get { - if (ui == null) return null; - return _consoleColorProxy ?? (_consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(ui))); + if (ui == null) + { + return null; + } + + return _consoleColorProxy ??= PSObject.AsPSObject(new ConsoleColorProxy(ui)); } } @@ -1045,18 +1155,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 } @@ -1071,17 +1179,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 } @@ -1150,6 +1267,10 @@ internal ConsoleHost() AppDomain.CurrentDomain.UnhandledException += handler; } + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Accesses instance members in preprocessor branch.")] private void BindBreakHandler() { #if UNIX @@ -1184,14 +1305,6 @@ private void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArg ui.WriteLine(); } - /// - /// Finalizes the instance. - /// - ~ConsoleHost() - { - Dispose(false); - } - /// /// Disposes of this instance, per the IDisposable pattern. /// @@ -1206,7 +1319,6 @@ private void Dispose(bool isDisposingNotFinalizing) if (!_isDisposed) { #if !UNIX - Dbg.Assert(breakHandlerGcHandle != null, "break handler should be set"); ConsoleControl.RemoveBreakHandler(); if (breakHandlerGcHandle.IsAllocated) { @@ -1221,15 +1333,8 @@ private void Dispose(bool isDisposingNotFinalizing) StopTranscribing(); } - if (_outputSerializer != null) - { - _outputSerializer.End(); - } - - if (_errorSerializer != null) - { - _errorSerializer.End(); - } + _outputSerializer?.End(); + _errorSerializer?.End(); if (_runspaceRef != null) { @@ -1251,6 +1356,14 @@ private void Dispose(bool isDisposingNotFinalizing) _isDisposed = true; } + /// + /// Finalizes an instance of the class. + /// + ~ConsoleHost() + { + Dispose(false); + } + /// /// Indicates if the session should be terminated or not. Typically set by the break handler for Close, Logoff, and /// Shutdown events. Note that the only valid transition for this property is from false to true: it is not legal to @@ -1280,7 +1393,7 @@ internal bool ShouldEndSession { // If ShouldEndSession is already true, you can't set it back - Dbg.Assert(_shouldEndSession != true || value, + Dbg.Assert(!_shouldEndSession || value, "ShouldEndSession can only be set from false to true"); _shouldEndSession = value; @@ -1314,7 +1427,7 @@ internal WrappedDeserializer.DataFormat ErrorFormat // If this shell is invoked in non-interactive, error is redirected, and OutputFormat was not // specified write data in error stream in xml format assuming PowerShell->PowerShell usage. - if (!OutputFormatSpecified && IsInteractive == false && Console.IsErrorRedirected && _wasInitialCommandEncoded) + if (!OutputFormatSpecified && !IsInteractive && Console.IsErrorRedirected && _wasInitialCommandEncoded) { format = Serialization.DataFormat.XML; } @@ -1337,14 +1450,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; } @@ -1354,14 +1464,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; } @@ -1453,7 +1560,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); @@ -1466,16 +1573,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) { @@ -1548,14 +1664,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) { @@ -1564,36 +1678,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. - /// - private bool IsScreenReaderActive() - { - if (_screenReaderActive.HasValue) - { - return _screenReaderActive.Value; - } - - _screenReaderActive = false; - if (Platform.IsWindowsDesktop) - { - // Note: this API can detect if a third-party screen reader is active, such as NVDA, but not the in-box Windows Narrator. - // Quoted from https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-systemparametersinfoa about the - // accessibility parameter 'SPI_GETSCREENREADER': - // "Narrator, the screen reader that is included with Windows, does not set the SPI_SETSCREENREADER or SPI_GETSCREENREADER flags." - bool enabled = false; - if (SystemParametersInfo(SPI_GETSCREENREADER, 0, ref enabled, 0)) - { - _screenReaderActive = enabled; - } - } - - return _screenReaderActive.Value; - } - - private bool LoadPSReadline() + private static bool LoadPSReadline() { // Don't load PSReadline if: // * we don't think the process will be interactive, e.g. -command or -file @@ -1610,20 +1695,39 @@ private 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; bool psReadlineFailed = false; // Load PSReadline by default unless there is no use: - // - screen reader is active, such as NVDA, indicating non-visual access // - we're running a command/file and just exiting // - stdin is redirected by a parent process // - we're not interactive @@ -1632,28 +1736,20 @@ 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()) + // Create and open Runspace with PSReadline. + defaultImportModulesList = DefaultInitialSessionState.Modules; + DefaultInitialSessionState.ImportPSModule(new[] { "PSReadLine" }); + consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); + try { - s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.PSReadLineDisabledWhenScreenReaderIsActive); - s_theConsoleHost.UI.WriteLine(); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } - else + catch (Exception) { - // Create and open Runspace with PSReadline. - defaultImportModulesList = DefaultInitialSessionState.Modules; - DefaultInitialSessionState.ImportPSModule(new[] { "PSReadLine" }); - consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); - try - { - OpenConsoleRunspace(consoleRunspace, staMode); - } - catch (Exception) - { - consoleRunspace = null; - psReadlineFailed = true; - } + consoleRunspace = null; + psReadlineFailed = true; } } @@ -1667,7 +1763,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; @@ -1699,10 +1795,10 @@ 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 void OpenConsoleRunspace(Runspace runspace, bool staMode) + private static void OpenConsoleRunspace(Runspace runspace, bool staMode) { if (staMode && Platform.IsWindowsDesktop) { @@ -1716,7 +1812,7 @@ private 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) { @@ -1751,13 +1847,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); @@ -1771,11 +1867,40 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, } else { - string shellId = "Microsoft.PowerShell"; + 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); @@ -1792,7 +1917,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: @@ -1810,7 +1935,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); } @@ -1837,24 +1962,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); } @@ -1862,7 +1989,7 @@ private void DoRunspaceInitialization(bool skipProfiles, string initialCommand, // If we're not going to continue, then get the exit code out of the runspace and // and indicate that it should be returned... - if (!_noExit && !(this.Runspace is RemoteRunspace)) + if (!_noExit && this.Runspace is not RemoteRunspace) { this.Runspace.ExecutionContext.ScriptCommandProcessorShouldRethrowExit = true; } @@ -1911,19 +2038,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); } @@ -1939,7 +2066,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; @@ -1998,7 +2125,6 @@ private void RunProfile(string profileFileName, Executor exec) /// /// /// - internal static string EscapeSingleQuotes(string str) { // worst case we have to escape every character, so capacity is twice as large as input length @@ -2022,7 +2148,7 @@ internal static string EscapeSingleQuotes(string str) private void WriteErrorLine(string line) { - ConsoleColor fg = ConsoleColor.Red; + const ConsoleColor fg = ConsoleColor.Red; ConsoleColor bg = UI.RawUI.BackgroundColor; UI.WriteLine(fg, bg, line); @@ -2046,9 +2172,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; } @@ -2077,7 +2201,6 @@ private void ReportException(Exception e, Executor exec) if (e1 != null) { // that didn't work. Write out the error ourselves as a last resort. - ReportExceptionFallback(e, null); } } @@ -2098,16 +2221,17 @@ private void ReportExceptionFallback(Exception e, string header) Console.Error.WriteLine(header); } - if (e == null) + if (e is null) { return; } // 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) { @@ -2124,8 +2248,22 @@ private void ReportExceptionFallback(Exception e, string header) } // Add the position message for the error if it's available. - if (er != null && er.InvocationInfo != null) + if (er?.InvocationInfo is { }) + { Console.Error.WriteLine(er.InvocationInfo.PositionMessage); + } + + // Print the stack trace. + Console.Error.WriteLine($"\n--- {e.GetType().FullName} ---"); + Console.Error.WriteLine(e.StackTrace); + + Exception inner = e.InnerException; + while (inner is { }) + { + Console.Error.WriteLine($"--- inner {inner.GetType().FullName} ---"); + Console.Error.WriteLine(inner.StackTrace); + inner = inner.InnerException; + } } /// @@ -2149,7 +2287,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; @@ -2161,10 +2302,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(); } // @@ -2209,10 +2347,7 @@ private void OnExecutionSuspended(object sender, DebuggerStopEventArgs e) finally { _debuggerStopEventArgs = null; - if (baseLoop != null) - { - baseLoop.ResumeCommandOutput(); - } + baseLoop?.ResumeCommandOutput(); } } @@ -2303,7 +2438,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) { @@ -2336,7 +2471,6 @@ internal static void RunNewInputLoop(ConsoleHost parent, bool isNested) /// /// when there is no instanceStack.Count == 0 /// - internal static bool ExitCurrentLoop() { if (s_instanceStack.Count == 0) @@ -2413,14 +2547,15 @@ 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) { @@ -2436,7 +2571,6 @@ internal void Run(bool inputLoopIsNested) if (inBlockMode) { // use a special prompt that denotes block mode - prompt = ">> "; } else @@ -2447,9 +2581,9 @@ internal void Run(bool inputLoopIsNested) ui.WriteLine(); // Evaluate any suggestions - if (!previousResponseWasEmpty) + if (previousResponseWasEmpty == false) { - EvaluateSuggestions(ui); + EvaluateFeedbacks(ui); } // Then output the prompt @@ -2458,15 +2592,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); @@ -2519,7 +2658,7 @@ internal void Run(bool inputLoopIsNested) if (inBlockMode) { s_tracer.WriteLine("adding line to block"); - inputBlock.Append("\n"); + inputBlock.Append('\n'); inputBlock.Append(line); continue; } @@ -2570,6 +2709,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); @@ -2586,10 +2733,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. @@ -2617,6 +2761,7 @@ e is RemoteException || #endif } } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure finally @@ -2628,8 +2773,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(); @@ -2642,8 +2786,7 @@ internal void BlockCommandOutput() internal void ResumeCommandOutput() { - RemotePipeline rCmdPipeline = _parent.runningCmd as RemotePipeline; - if (rCmdPipeline != null) + if (_parent.runningCmd is RemotePipeline rCmdPipeline) { rCmdPipeline.ResumeIncomingData(); } @@ -2671,7 +2814,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); } @@ -2724,7 +2867,7 @@ private DebuggerCommandResults ProcessDebugCommand(string cmd, out Exception e) return results ?? new DebuggerCommandResults(DebuggerResumeAction.Continue, false); } - private bool IsIncompleteParseException(Exception e) + private static bool IsIncompleteParseException(Exception e) { // Check e's type. if (e is IncompleteParseException) @@ -2733,42 +2876,26 @@ private 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; } - return remoteException.ErrorRecord.CategoryInfo.Reason == typeof(IncompleteParseException).Name; + return remoteException.ErrorRecord.CategoryInfo.Reason == nameof(IncompleteParseException); } - private void EvaluateSuggestions(ConsoleHostUserInterface ui) + private void EvaluateFeedbacks(ConsoleHostUserInterface ui) { // Output any training suggestions try { - List suggestions = HostUtilities.GetSuggestion(_parent.Runspace); - - if (suggestions.Count > 0) + List feedbacks = FeedbackHub.GetFeedback(_parent.Runspace); + if (feedbacks is null || feedbacks.Count is 0) { - ui.WriteLine(); + return; } - bool first = true; - foreach (string suggestion in suggestions) - { - if (!first) - ui.WriteLine(); - - ui.WriteLine(suggestion); - - first = false; - } - } - catch (TerminateException) - { - // A variable breakpoint may be hit by HostUtilities.GetSuggestion. The debugger throws TerminateExceptions to stop the execution - // of the current statement; we do not want to treat these exceptions as errors. + HostUtilities.RenderFeedback(feedbacks, ui); } catch (Exception e) { @@ -2782,8 +2909,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)) { @@ -2793,8 +2919,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); } @@ -2828,24 +2953,20 @@ 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; } - private ConsoleHost _parent; - private bool _isNested; + private readonly ConsoleHost _parent; + private readonly bool _isNested; private bool _shouldExit; - private Executor _exec; - private Executor _promptExec; - private object _syncObject = new object(); + private readonly Executor _exec; + private readonly Executor _promptExec; + private readonly object _syncObject = new object(); private bool _isRunspacePushed = false; private bool _runspacePopped = false; @@ -2854,41 +2975,28 @@ private string EvaluateDebugPrompt() // threadsafety guaranteed by enclosing class - private static Stack s_instanceStack = new Stack(); + 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() - : - base() + : base() { } internal ConsoleHostStartupException(string message) - : - base(message) - { - } - - protected - ConsoleHostStartupException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : - base(info, context) + : base(message) { } internal ConsoleHostStartupException(string message, Exception innerException) - : - base(message, innerException) + : base(message, innerException) { } } @@ -2909,22 +3017,21 @@ private class ConsoleHostStartupException : Exception // Set to Unknown so that we avoid saving/restoring the console mode if we don't have a console. private ConsoleControl.ConsoleModes _savedConsoleMode = ConsoleControl.ConsoleModes.Unknown; - private ConsoleControl.ConsoleModes _initialConsoleMode = ConsoleControl.ConsoleModes.Unknown; + private readonly ConsoleControl.ConsoleModes _initialConsoleMode = ConsoleControl.ConsoleModes.Unknown; #endif private Thread _breakHandlerThread; 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 Version _ver = PSVersionInfo.PSVersion; + private readonly Version _ver = PSVersionInfo.PSVersion; private int _exitCodeFromRunspace; private bool _noExit = true; private bool _setShouldExitCalled; private bool _isRunningPromptLoop; private bool _wasInitialCommandEncoded; - private bool? _screenReaderActive; // hostGlobalLock is used to sync public method calls (in case multiple threads call into the host) and access to // state that persists across method calls, like progress data. It's internal because the ui object also @@ -2938,7 +3045,7 @@ private class ConsoleHostStartupException : Exception private bool _shouldEndSession; private int _beginApplicationNotifyCount; - private ConsoleTextWriter _consoleWriter; + private readonly ConsoleTextWriter _consoleWriter; private WrappedSerializer _outputSerializer; private WrappedSerializer _errorSerializer; private bool _displayDebuggerBanner; @@ -2954,11 +3061,10 @@ private class ConsoleHostStartupException : Exception internal static InitialSessionState DefaultInitialSessionState; [TraceSource("ConsoleHost", "ConsoleHost subclass of S.M.A.PSHost")] - private static - PSTraceSource s_tracer = PSTraceSource.GetTracer("ConsoleHost", "ConsoleHost subclass of S.M.A.PSHost"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ConsoleHost", "ConsoleHost subclass of S.M.A.PSHost"); [TraceSource("ConsoleHostRunspaceInit", "Initialization code for ConsoleHost's Runspace")] - private static PSTraceSource s_runspaceInitTracer = + private static readonly PSTraceSource s_runspaceInitTracer = PSTraceSource.GetTracer("ConsoleHostRunspaceInit", "Initialization code for ConsoleHost's Runspace", false); } @@ -2975,20 +3081,27 @@ internal RunspaceCreationEventArgs( bool skipProfiles, bool staMode, string configurationName, + string configurationFilePath, Collection initialCommandArgs) { InitialCommand = initialCommand; SkipProfiles = skipProfiles; StaMode = staMode; ConfigurationName = configurationName; + ConfigurationFilePath = configurationFilePath; InitialCommandArgs = initialCommandArgs; } internal string InitialCommand { get; set; } + internal bool SkipProfiles { get; set; } + internal bool StaMode { get; set; } + 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 91aceef2fbd..58630f5b3c0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostRawUserInterface.cs @@ -4,25 +4,23 @@ #if !UNIX using System; -using System.Management.Automation; -using System.Management.Automation.Internal; -using System.Management.Automation.Host; using System.ComponentModel; using System.Globalization; +using System.Management.Automation; +using System.Management.Automation.Host; +using System.Management.Automation.Internal; using System.Security.Principal; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Dbg = System.Management.Automation.Diagnostics; using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; +using Dbg = System.Management.Automation.Diagnostics; using WORD = System.UInt16; -using DWORD = System.UInt32; namespace Microsoft.PowerShell { /// /// Implementation of RawConsole for powershell. /// - internal sealed class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawUserInterface { @@ -31,7 +29,6 @@ class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawU /// /// If obtaining the buffer's foreground and background color failed /// - internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base() { @@ -44,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); } } }); @@ -78,7 +68,6 @@ class ConsoleHostRawUserInterface : System.Management.Automation.Host.PSHostRawU /// OR /// Win32's SetConsoleTextAttribute /// - public override ConsoleColor ForegroundColor @@ -89,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; } @@ -129,7 +117,6 @@ public override /// OR /// Win32's SetConsoleTextAttribute /// - public override ConsoleColor BackgroundColor @@ -140,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; } @@ -195,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 + } } } @@ -222,7 +209,6 @@ public override /// OR /// Win32's SetConsoleCursorInfo failed /// - public override int CursorSize @@ -278,7 +264,6 @@ public override /// OR /// Win32's SetConsoleWindowInfo failed /// - public override Coordinates WindowPosition @@ -345,7 +330,6 @@ public override /// OR /// Win32's SetConsoleScreenBufferSize failed /// - public override Size BufferSize @@ -370,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, @@ -398,7 +381,6 @@ public override /// OR /// Win32's SetConsoleWindowInfo failed /// - public override Size WindowSize @@ -465,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; @@ -519,7 +501,6 @@ public override /// /// If obtaining information about the buffer failed /// - public override Size MaxWindowSize @@ -543,7 +524,6 @@ public override /// OR /// Win32's GetLargestConsoleWindowSize failed /// - public override Size MaxPhysicalWindowSize @@ -559,7 +539,7 @@ public override /// Helper method to create and trace PipelineStoppedException. /// /// - private PipelineStoppedException NewPipelineStoppedException() + private static PipelineStoppedException NewPipelineStoppedException() { PipelineStoppedException e = new PipelineStoppedException(); return e; @@ -602,7 +582,6 @@ private static void CacheKeyEvent(ConsoleControl.KEY_EVENT_RECORD input, ref Con /// OR /// Win32's ReadConsoleInput failed /// - public override KeyInfo ReadKey(ReadKeyOptions options) @@ -659,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) == @@ -730,7 +708,6 @@ private static /// OR /// Win32's FlushConsoleInputBuffer failed /// - public override void FlushInputBuffer() @@ -752,7 +729,6 @@ public override /// OR /// Win32's PeekConsoleInput failed /// - public override bool KeyAvailable @@ -873,7 +849,6 @@ public override string WindowTitle /// OR /// there is not enough memory to complete calls to Win32's WriteConsoleOutput /// - public override void SetBufferContents(Coordinates origin, BufferCell[,] contents) @@ -1084,7 +1059,6 @@ public override /// OR /// there is not enough memory to complete calls to Win32's ReadConsoleOutput /// - public override BufferCell[,] GetBufferContents(Rectangle region) { @@ -1208,7 +1182,6 @@ BufferCell fill /// /// If Win32's WideCharToMultiByte fails /// - public override int LengthInBufferCells(string s) { @@ -1224,7 +1197,6 @@ int LengthInBufferCells(string s) /// /// If Win32's WideCharToMultiByte fails /// - public override int LengthInBufferCells(string s, int offset) { @@ -1244,7 +1216,6 @@ int LengthInBufferCells(string s, int offset) /// /// If Win32's WideCharToMultiByte fails /// - public override int LengthInBufferCells(char c) { @@ -1317,17 +1288,16 @@ private static #endregion helpers - private ConsoleColor defaultForeground = ConsoleColor.Gray; + private readonly ConsoleColor defaultForeground = ConsoleColor.Gray; - private ConsoleColor defaultBackground = ConsoleColor.Black; + private readonly ConsoleColor defaultBackground = ConsoleColor.Black; - private ConsoleHostUserInterface parent = null; + private readonly ConsoleHostUserInterface parent = null; private ConsoleControl.KEY_EVENT_RECORD cachedKeyEvent; [TraceSourceAttribute("ConsoleHostRawUserInterface", "Console host's subclass of S.M.A.Host.RawConsole")] - private static - PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleHostRawUserInterface", "Console host's subclass of S.M.A.Host.RawConsole"); + private static readonly PSTraceSource tracer = PSTraceSource.GetTracer("ConsoleHostRawUserInterface", "Console host's subclass of S.M.A.Host.RawConsole"); } } // namespace @@ -1354,7 +1324,7 @@ namespace Microsoft.PowerShell internal sealed class ConsoleHostRawUserInterface : PSHostRawUserInterface { - private ConsoleHostUserInterface _parent = null; + private readonly ConsoleHostUserInterface _parent = null; internal ConsoleHostRawUserInterface(ConsoleHostUserInterface mshConsole) : base() { @@ -1389,7 +1359,10 @@ public override Size BufferSize : new Size(Console.BufferWidth, Console.BufferHeight); } - set { Console.SetBufferSize(value.Width, value.Height); } + set + { + Console.SetBufferSize(value.Width, value.Height); + } } /// @@ -1397,7 +1370,10 @@ public override Size BufferSize /// public override Coordinates CursorPosition { - get { return new Coordinates(Console.CursorLeft, Console.CursorTop); } + get + { + return new Coordinates(Console.CursorLeft, Console.CursorTop); + } set { @@ -1495,7 +1471,10 @@ public override Size WindowSize : new Size(Console.WindowWidth, Console.WindowHeight); } - set { Console.SetWindowSize(value.Width, value.Height); } + set + { + Console.SetWindowSize(value.Width, value.Height); + } } /// @@ -1551,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}"); } } @@ -1567,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/ConsoleHostTranscript.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs index fd1b91574b4..3ac758ff86c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostTranscript.cs @@ -6,8 +6,6 @@ using System.Management.Automation.Host; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation.Diagnostics; - namespace Microsoft.PowerShell { internal sealed partial class ConsoleHost : PSHost, IDisposable @@ -61,7 +59,7 @@ internal void StartTranscribing(string transcriptFilename, bool shouldAppend) } } */ - private string _transcriptFileName = string.Empty; + private readonly string _transcriptFileName = string.Empty; internal string StopTranscribing() { @@ -127,7 +125,6 @@ internal void WriteToTranscript(ReadOnlySpan text, bool newLine) } private StreamWriter _transcriptionWriter; - private object _transcriptionStateLock = new object(); + private readonly object _transcriptionStateLock = new object(); } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 483fdb4625d..f0da0d99547 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -42,7 +42,7 @@ internal partial class ConsoleHostUserInterface : System.Management.Automation.H /// /// This is a test hook for programmatically reading and writing ConsoleHost I/O. /// - private static PSHostUserInterface s_h = null; + private static readonly PSHostUserInterface s_h = null; /// /// Return true if the console supports a VT100 like virtual terminal. @@ -54,38 +54,76 @@ internal partial class ConsoleHostUserInterface : System.Management.Automation.H /// /// /// - internal ConsoleHostUserInterface(ConsoleHost parent) { Dbg.Assert(parent != null, "parent may not be null"); _parent = parent; _rawui = new ConsoleHostRawUserInterface(this); + 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 TryTurnOnVirtualTerminal() + { #if UNIX - SupportsVirtualTerminal = true; + return true; #else 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); - this.SupportsVirtualTerminal = (m & ConsoleControl.ConsoleModes.VirtualTerminal) != 0; + outputMode = ConsoleControl.GetMode(outputHandle); + return outputMode.HasFlag(ConsoleControl.ConsoleModes.VirtualTerminal); } } catch { + // Do nothing if failed } -#endif - _isInteractiveTestToolListening = false; + return false; +#endif } /// @@ -93,7 +131,6 @@ internal ConsoleHostUserInterface(ConsoleHost parent) /// /// /// - public override PSHostRawUserInterface RawUI { get @@ -131,7 +168,6 @@ public override PSHostRawUserInterface RawUI /// /// True if command completion is currently running. /// - internal bool IsCommandCompletionRunning { get @@ -144,13 +180,11 @@ internal bool IsCommandCompletionRunning /// /// True if the Read* functions should read from the stdin stream instead of from the win32 console. /// - internal bool ReadFromStdin { get; set; } /// /// True if the host shouldn't write out prompts. /// - internal bool NoPrompt { get; set; } #region Line-oriented interaction @@ -168,48 +202,13 @@ internal bool IsCommandCompletionRunning /// OR /// Win32's SetConsoleCursorPosition failed /// - 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); - } - - /// - /// See base class. - /// - /// - /// The characters typed by the user. - /// - /// - /// If obtaining a handle to the active screen buffer failed - /// OR - /// Win32's setting input buffer mode to disregard window and mouse input failed. - /// OR - /// Win32's ReadConsole failed. - /// - /// - /// If Ctrl-C is entered by user. - /// - public override string ReadLineMaskedAsString() - { - HandleThrowOnReadAndPrompt(); - - // we lock here so that multiple threads won't interleave the various reads and writes here. - object result = null; - lock (_instanceLock) - { - result = ReadLineSafe(false, PrintToken); - } - - StringBuilder resultSb = result as StringBuilder; - Dbg.Assert(resultSb != null, "ReadLineMaskedAsString did not return a stringBuilder"); - return resultSb.ToString(); + return ReadLine(false, string.Empty, out _, true, true); } /// @@ -226,7 +225,6 @@ public override string ReadLineMaskedAsString() /// /// If Ctrl-C is entered by user /// - public override SecureString ReadLineAsSecureString() { HandleThrowOnReadAndPrompt(); @@ -257,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 /// /// @@ -278,7 +276,6 @@ public override SecureString ReadLineAsSecureString() /// /// If Ctrl-C is entered by user /// - private object ReadLineSafe(bool isSecureString, char? printToken) { // Don't lock (instanceLock) in here -- the caller needs to do that... @@ -335,18 +332,19 @@ private object ReadLineSafe(bool isSecureString, char? printToken) Coordinates originalCursorPos = _rawui.CursorPosition; - do + // + // 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; - Span inputBuffer = stackalloc char[CharactersToRead + 1]; string key = ConsoleControl.ReadConsole(handle, initialContentLength: 0, inputBuffer, charactersToRead: CharactersToRead, endOnTab: false, out _); #endif @@ -354,7 +352,7 @@ private object ReadLineSafe(bool isSecureString, char? printToken) // Handle Ctrl-C ending input if (keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers.HasFlag(ConsoleModifiers.Control)) #else - if (string.IsNullOrEmpty(key) || (char)3 == key[0]) + if (string.IsNullOrEmpty(key) || key[0] == (char)3) #endif { PipelineStoppedException e = new PipelineStoppedException(); @@ -363,7 +361,7 @@ private object ReadLineSafe(bool isSecureString, char? printToken) #if UNIX if (keyInfo.Key == ConsoleKey.Enter) #else - if ((char)13 == key[0]) + if (key[0] == (char)13) #endif { // @@ -374,7 +372,7 @@ private object ReadLineSafe(bool isSecureString, char? printToken) #if UNIX if (keyInfo.Key == ConsoleKey.Backspace) #else - if ((char)8 == key[0]) + if (key[0] == (char)8) #endif { // @@ -426,7 +424,6 @@ private object ReadLineSafe(bool isSecureString, char? printToken) } } } - while (true); } #if UNIX catch (InvalidOperationException) @@ -473,7 +470,6 @@ private object ReadLineSafe(bool isSecureString, char? printToken) /// OR /// Win32's SetConsoleCursorPosition failed /// - private void WritePrintToken( string printToken, ref Coordinates originalCursorPosition) @@ -510,7 +506,6 @@ private void WritePrintToken( /// OR /// Win32's SetConsoleCursorPosition failed /// - private void WriteBackSpace(Coordinates originalCursorPosition) { Coordinates cursorPosition = _rawui.CursorPosition; @@ -583,7 +578,7 @@ private static bool shouldUnsetMode( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteToConsole(char c, bool transcribeResult) { - ReadOnlySpan value = stackalloc char[1] { c }; + ReadOnlySpan value = [c]; WriteToConsole(value, transcribeResult); } @@ -661,7 +656,7 @@ private void WriteToConsole(ConsoleColor foregroundColor, ConsoleColor backgroun } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ConsoleOutWriteHelper(ReadOnlySpan value, bool newLine) + private static void ConsoleOutWriteHelper(ReadOnlySpan value, bool newLine) { if (newLine) { @@ -712,12 +707,16 @@ private void WriteLineToConsole() /// OR /// Win32's WriteConsole fails /// - 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) @@ -739,6 +738,7 @@ private void WriteImpl(string value, bool newLine) } TextWriter writer = Console.IsOutputRedirected ? Console.Out : _parent.ConsoleTextWriter; + value = GetOutputString(value, SupportsVirtualTerminal); if (_parent.IsRunningAsync) { @@ -783,7 +783,6 @@ private void WriteImpl(string value, bool newLine) /// OR /// Win32's WriteConsole fails /// - public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) { Write(foregroundColor, backgroundColor, value, newLine: false); @@ -851,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); + } } /// @@ -868,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 @@ -895,7 +900,6 @@ public override void WriteLine() /// A list of strings representing the text broken into "lines" each of which are guaranteed not to exceed /// maxWidthInBufferCells. /// - internal List WrapText(string text, int maxWidthInBufferCells) { List result = new List(); @@ -969,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"); @@ -987,7 +991,6 @@ internal List WrapText(string text, int maxWidthInBufferCells) /// /// Struct used by WrapText. /// - [Flags] internal enum WordFlags { @@ -1022,7 +1025,6 @@ internal struct Word /// This can be made faster by, instead of creating little strings for each word, creating indices of the start and end /// range of a word. That would reduce the string allocations. /// - internal List ChopTextIntoWords(string text, int maxWidthInBufferCells) { List result = new List(); @@ -1130,7 +1132,6 @@ internal List ChopTextIntoWords(string text, int maxWidthInBufferCells) /// /// The list into which the words will be added. /// - internal void AddWord(string text, int startIndex, int endIndex, int maxWidthInBufferCells, bool isWhitespace, ref List result) { @@ -1149,7 +1150,7 @@ internal void AddWord(string text, int startIndex, int endIndex, w.Flags = WordFlags.IsWhitespace; } - do + while (true) { w.Text = text.Substring(startIndex, i - startIndex); w.CellCount = RawUI.LengthInBufferCells(w.Text); @@ -1165,7 +1166,7 @@ internal void AddWord(string text, int startIndex, int endIndex, --i; } - } while (true); + } Dbg.Assert(RawUI.LengthInBufferCells(w.Text) <= maxWidthInBufferCells, "word should not exceed max"); result.Add(w); @@ -1216,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) { @@ -1227,11 +1224,17 @@ public override void WriteDebugLine(string message) } else { - // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here... - WriteLine( - DebugForegroundColor, - DebugBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message)); + if (SupportsVirtualTerminal) + { + WriteLine(GetFormatStyleString(FormatStyle.Debug) + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message) + PSStyle.Instance.Reset); + } + else + { + WriteLine( + DebugForegroundColor, + DebugBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.DebugFormatString, message)); + } } } @@ -1269,13 +1272,8 @@ public override void WriteInformation(InformationRecord record) /// OR /// Win32's WriteConsole fails /// - 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) { @@ -1283,10 +1281,17 @@ public override void WriteVerboseLine(string message) } else { - WriteLine( - VerboseForegroundColor, - VerboseBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message)); + if (SupportsVirtualTerminal) + { + WriteLine(GetFormatStyleString(FormatStyle.Verbose) + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message) + PSStyle.Instance.Reset); + } + else + { + WriteLine( + VerboseForegroundColor, + VerboseBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.VerboseFormatString, message)); + } } } @@ -1307,13 +1312,8 @@ public override void WriteVerboseLine(string message) /// OR /// Win32's WriteConsole fails /// - 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) { @@ -1321,46 +1321,45 @@ public override void WriteWarningLine(string message) } else { - WriteLine( - WarningForegroundColor, - WarningBackgroundColor, - StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message)); + if (SupportsVirtualTerminal) + { + WriteLine(GetFormatStyleString(FormatStyle.Warning) + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, message) + PSStyle.Instance.Reset); + } + else + { + WriteLine( + WarningForegroundColor, + WarningBackgroundColor, + StringUtil.Format(ConsoleHostUserInterfaceStrings.WarningFormatString, 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) { - if (record == null) + Dbg.Assert(record != null, "WriteProgress called with null ProgressRecord"); + + if (_parent.ErrorFormat == Serialization.DataFormat.XML) { - Dbg.Assert(false, "WriteProgress called with null ProgressRecord"); + PSObject obj = new PSObject(); + obj.Properties.Add(new PSNoteProperty("SourceId", sourceId)); + 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 { - 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) + lock (_instanceLock) { - PSObject obj = new PSObject(); - obj.Properties.Add(new PSNoteProperty("SourceId", sourceId)); - obj.Properties.Add(new PSNoteProperty("Record", record)); - _parent.ErrorSerializer.Serialize(obj, "progress"); - } - else - { - lock (_instanceLock) - { - HandleIncomingProgressRecord(sourceId, record); - } + HandleIncomingProgressRecord(sourceId, record); } } } @@ -1385,34 +1384,46 @@ public override void WriteErrorLine(string value) else { if (writer == _parent.ConsoleTextWriter) - WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + { + if (SupportsVirtualTerminal) + { + WriteLine(value); + } + else + { + WriteLine(ErrorForegroundColor, ErrorBackgroundColor, value); + } + } else + { + value = GetOutputString(value, SupportsVirtualTerminal); Console.Error.WriteLine(value); + } } } - // Format colors public ConsoleColor FormatAccentColor { get; set; } = ConsoleColor.Green; - // Error colors public ConsoleColor ErrorAccentColor { get; set; } = ConsoleColor.Cyan; + public ConsoleColor ErrorForegroundColor { get; set; } = ConsoleColor.Red; + public ConsoleColor ErrorBackgroundColor { get; set; } = Console.BackgroundColor; - // Warning colors public ConsoleColor WarningForegroundColor { get; set; } = ConsoleColor.Yellow; + public ConsoleColor WarningBackgroundColor { get; set; } = Console.BackgroundColor; - // Debug colors public ConsoleColor DebugForegroundColor { get; set; } = ConsoleColor.Yellow; + public ConsoleColor DebugBackgroundColor { get; set; } = Console.BackgroundColor; - // Verbose colors public ConsoleColor VerboseForegroundColor { get; set; } = ConsoleColor.Yellow; + public ConsoleColor VerboseBackgroundColor { get; set; } = Console.BackgroundColor; - // Progress colors public ConsoleColor ProgressForegroundColor { get; set; } = ConsoleColor.Black; + public ConsoleColor ProgressBackgroundColor { get; set; } = ConsoleColor.Yellow; #endregion Line-oriented interaction @@ -1470,13 +1481,15 @@ internal enum ReadLineResult /// OR /// Win32's SetConsoleCursorPosition failed /// - internal string ReadLine(bool endOnTab, string initialContent, out ReadLineResult result, bool calledFromPipeline, bool transcribeResult) { 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; @@ -1521,14 +1534,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(); } @@ -1621,7 +1641,7 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca } #endif - do + while (true) { #if UNIX keyInfo = Console.ReadKey(true); @@ -1848,7 +1868,6 @@ private string ReadLineFromConsole(bool endOnTab, string initialContent, bool ca Console.CursorLeft = cursorCurrent + 1; #endif } - while (true); Dbg.Assert( (s == null && result == ReadLineResult.endedOnBreak) @@ -1896,22 +1915,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... - private string RemoveNulls(string input) + /// 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(); @@ -1949,7 +1970,7 @@ internal string ReadLineWithTabCompletion(Executor exec) string completionInput = null; #endif - do + while (true) { if (TryInvokeUserDefinedReadLine(out input)) { @@ -2003,8 +2024,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 { @@ -2081,7 +2101,6 @@ internal string ReadLineWithTabCompletion(Executor exec) } #endif } - while (true); // Since we did not transcribe any call to ReadLine, transcribe the results here. @@ -2096,26 +2115,26 @@ internal string ReadLineWithTabCompletion(Executor exec) } #if !UNIX - private void SendLeftArrows(int length) + private static void SendLeftArrows(int length) { var inputs = new ConsoleControl.INPUT[length * 2]; 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; @@ -2212,7 +2231,7 @@ private bool TryInvokeUserDefinedReadLine(out string input) // used to serialize access to instance data - private object _instanceLock = new object(); + private readonly object _instanceLock = new object(); // If this is true, class throws on read or prompt method which require // access to console. @@ -2236,16 +2255,14 @@ internal void HandleThrowOnReadAndPrompt() // this is a test hook for the ConsoleInteractiveTestTool, which sets this field to true. - private bool _isInteractiveTestToolListening; + private readonly bool _isInteractiveTestToolListening; // This instance data is "read-only" and need not have access serialized. - private ConsoleHostRawUserInterface _rawui; - private ConsoleHost _parent; + private readonly ConsoleHostRawUserInterface _rawui; + private readonly ConsoleHost _parent; - [TraceSourceAttribute("ConsoleHostUserInterface", "Console host's subclass of S.M.A.Host.Console")] - private static - PSTraceSource s_tracer = PSTraceSource.GetTracer("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 ffc5735f35f..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,13 +11,12 @@ 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 /// outstanding progress activity state. /// - internal void ResetProgress() @@ -48,6 +48,13 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt } _pendingProgress = null; + + if (SupportsVirtualTerminal && !PSHost.IsStdOutputRedirected && PSStyle.Instance.Progress.UseOSCIndicator) + { + // OSC sequence to turn off progress indicator + // https://github.com/microsoft/terminal/issues/6700 + Console.Write("\x1b]9;4;0\x1b\\"); + } } } @@ -55,10 +62,9 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt /// Invoked by ConsoleHostUserInterface.WriteProgress to update the set of outstanding activities for which /// ProgressRecords have been received. /// - private void - HandleIncomingProgressRecord(Int64 sourceId, ProgressRecord record) + HandleIncomingProgressRecord(long sourceId, ProgressRecord record) { Dbg.Assert(record != null, "record should not be null"); @@ -94,6 +100,27 @@ 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 (SupportsVirtualTerminal && !PSHost.IsStdOutputRedirected && PSStyle.Instance.Progress.UseOSCIndicator) + { + int percentComplete = record.PercentComplete; + if (percentComplete < 0) + { + // Write-Progress allows for negative percent complete, but not greater than 100 + // but OSC sequence is limited from 0 to 100. + percentComplete = 0; + } + + // OSC sequence to turn on progress indicator + // https://github.com/microsoft/terminal/issues/6700 + 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); } } @@ -101,7 +128,6 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt /// /// TimerCallback for '_progPaneUpdateTimer' to update 'progPaneUpdateFlag' /// - private void ProgressPaneUpdateTimerElapsed(object sender) @@ -113,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 @@ -152,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 @@ -198,4 +212,3 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt private int progPaneUpdateFlag = 0; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs index 0afe5f987d8..5895dcf2b83 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePrompt.cs @@ -87,7 +87,6 @@ private static /// If the converting the user input to the prompt field type fails unless it is caused by /// OverflowException or FormatException /// - public override Dictionary Prompt(string caption, string message, Collection descriptions) @@ -141,21 +140,19 @@ 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; string fieldPrompt = null; fieldPrompt = desc.Name; - bool fieldEchoOnPrompt = true; - // FieldDescription.ParameterAssemblyFullName never returns null. But this is // defense in depth. 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); } @@ -193,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; } @@ -202,18 +198,27 @@ public override StringBuilder fieldPromptList = new StringBuilder(fieldPrompt); // fieldPromptList = fieldPrompt + "[i] :" - fieldPromptList.Append("["); + fieldPromptList.Append('['); while (true) { - fieldPromptList.Append( - string.Format(CultureInfo.InvariantCulture, "{0}]: ", inputList.Count)); - bool inputListEnd = false; + fieldPromptList.Append(CultureInfo.InvariantCulture, $"{inputList.Count}]: "); + bool endListInput = false; object convertedObj = null; - string inputString = PromptForSingleItem(elementType, fieldPromptList.ToString(), fieldPrompt, caption, message, - desc, fieldEchoOnPrompt, true, out inputListEnd, out cancelInput, out convertedObj); - - if (cancelInput || inputListEnd) + _ = PromptForSingleItem( + elementType, + fieldPromptList.ToString(), + fieldPrompt, + caption, + message, + desc, + fieldEchoOnPrompt: true, + listInput: true, + out endListInput, + out cancelInput, + out convertedObj); + + if (cancelInput || endListInput) { break; } @@ -244,10 +249,20 @@ public override fieldPrompt); // field is not a list object convertedObj = null; - bool dummy = false; - PromptForSingleItem(fieldType, printFieldPrompt, fieldPrompt, caption, message, desc, - fieldEchoOnPrompt, false, out dummy, out cancelInput, out convertedObj); + _ = PromptForSingleItem( + fieldType, + printFieldPrompt, + fieldPrompt, + caption, + message, + desc, + fieldEchoOnPrompt: true, + listInput: false, + endListInput: out _, + out cancelInput, + out convertedObj); + if (!cancelInput) { inputPSObject = PSObject.AsPSObject(convertedObj); @@ -339,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) @@ -469,17 +484,16 @@ 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. /// /// /// /// /// - 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); @@ -544,4 +558,3 @@ private void ReportUnrecognizedPromptCommand(string command) private const string PromptCommandPrefix = "!"; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs index 70163c865e4..7e3dcfd70e0 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfacePromptForChoice.cs @@ -9,10 +9,7 @@ using System.Management.Automation.Host; using System.Management.Automation.Internal; using System.Text; - using Dbg = System.Management.Automation.Diagnostics; -using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; -using NakedWin32Handle = System.IntPtr; namespace Microsoft.PowerShell { @@ -39,7 +36,6 @@ internal partial class ConsoleHostUserInterface : PSHostUserInterface, IHostUISu /// /// when prompt is canceled by, for example, Ctrl-c. /// - public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) { HandleThrowOnReadAndPrompt(); @@ -91,7 +87,7 @@ public override int PromptForChoice(string caption, string message, Collection PromptForChoice(string caption, // used to display ChoiceMessage like Choice[0],Choice[1] etc int choicesSelected = 0; - do + while (true) { // write the current prompt string choiceMsg = StringUtil.Format(ConsoleHostUserInterfaceStrings.ChoiceMessage, choicesSelected); @@ -257,7 +252,7 @@ public Collection PromptForChoice(string caption, // choices to be picked. // user did not pick up any choices..choose the default - if ((result.Count == 0) && (defaultChoiceKeys.Keys.Count >= 0)) + if (result.Count == 0) { // if there's a default, pick that one. foreach (int defaultChoice in defaultChoiceKeys.Keys) @@ -286,7 +281,6 @@ public Collection PromptForChoice(string caption, } // prompt for multiple choices } - while (true); return result; } @@ -303,7 +297,7 @@ private void WriteChoicePrompt(string[,] hotkeysAndPlainLabels, int lineLenMax = RawUI.WindowSize.Width - 1; int lineLen = 0; - string choiceTemplate = "[{0}] {1} "; + const string choiceTemplate = "[{0}] {1} "; for (int i = 0; i < hotkeysAndPlainLabels.GetLength(1); ++i) { @@ -351,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 = ","; } @@ -433,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/ConsoleHostUserInterfaceSecurity.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs index 6926f685795..1e934553f18 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterfaceSecurity.cs @@ -1,14 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Globalization; using System.Management.Automation; using System.Management.Automation.Internal; using System.Security; -using Microsoft.Win32; - namespace Microsoft.PowerShell { /// @@ -29,7 +25,6 @@ class ConsoleHostUserInterface : System.Management.Automation.Host.PSHostUserInt /// Message to be displayed. /// Caption for the message. /// PSCredential object. - public override PSCredential PromptForCredential( string caption, string message, @@ -54,7 +49,6 @@ public override PSCredential PromptForCredential( /// What type of creds can be supplied by the user. /// Options that control the cred gathering UI behavior. /// PSCredential object, or null if input was cancelled (or if reading from stdin and stdin at EOF). - public override PSCredential PromptForCredential( string caption, string message, @@ -103,17 +97,26 @@ public override PSCredential PromptForCredential( passwordPrompt = StringUtil.Format(ConsoleHostUserInterfaceSecurityResources.PromptForCredential_Password, userName ); - // - // now, prompt for the password - // - WriteToConsole(passwordPrompt, true); - password = ReadLineAsSecureString(); - if (password == null) + if (!InternalTestHooks.NoPromptForPassword) + { + WriteToConsole(passwordPrompt, transcribeResult: true); + password = ReadLineAsSecureString(); + if (password == null) + { + return null; + } + + WriteLineToConsole(); + } + else { - return null; + password = new SecureString(); } - WriteLineToConsole(); + if (!string.IsNullOrEmpty(targetName)) + { + userName = StringUtil.Format("{0}\\{1}", targetName, userName); + } cred = new PSCredential(userName, password); @@ -121,4 +124,3 @@ public override PSCredential PromptForCredential( } } } - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs index cd8364fc7ce..ce5d5ecaef4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleShell.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System.Management.Automation; using System.Management.Automation.Runspaces; -using System.Runtime.CompilerServices; 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 { /// Entry point in to ConsoleShell. This method is called by main of minishell. @@ -19,9 +19,14 @@ public static class ConsoleShell /// Help text for minishell. This is displayed on 'minishell -?'. /// Commandline parameters specified by user. /// An integer value which should be used as exit code for the process. - public static int Start(string bannerText, string helpText, string[] args) + 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. @@ -30,7 +35,32 @@ public static int Start(string bannerText, string helpText, string[] args) /// Help text for the shell. /// 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) + 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) { @@ -42,9 +72,10 @@ public static int Start(InitialSessionState initialSessionState, string bannerTe throw PSTraceSource.NewArgumentNullException(nameof(args)); } + ConsoleHost.ParseCommandLine(args); ConsoleHost.DefaultInitialSessionState = initialSessionState; - return ConsoleHost.Start(bannerText, helpText, args); + return ConsoleHost.Start(bannerText, helpText, issProvided); } } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs index c875bc0c506..a82156ce1c8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs @@ -4,12 +4,7 @@ using System; using System.IO; using System.Text; - -using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle; using Dbg = System.Management.Automation.Diagnostics; -using DWORD = System.UInt32; -using HRESULT = System.UInt32; -using NakedWin32Handle = System.IntPtr; namespace Microsoft.PowerShell { @@ -18,8 +13,7 @@ class ConsoleTextWriter : TextWriter { internal ConsoleTextWriter(ConsoleHostUserInterface ui) - : - base(System.Globalization.CultureInfo.CurrentCulture) + : base(System.Globalization.CultureInfo.CurrentCulture) { Dbg.Assert(ui != null, "ui needs a value"); @@ -82,8 +76,8 @@ public override void Write(char c) { - ReadOnlySpan c1 = stackalloc char[1] { c }; - _ui.WriteToConsole(c1, transcribeResult: true); + ReadOnlySpan value = [c]; + _ui.WriteToConsole(value, transcribeResult: true); } public override @@ -93,6 +87,6 @@ public override _ui.WriteToConsole(a, transcribeResult: true); } - private ConsoleHostUserInterface _ui; + private readonly ConsoleHostUserInterface _ui; } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index 24b48837005..354c61fb8f3 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -2,12 +2,10 @@ // Licensed under the MIT License. using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Management.Automation; -using System.Management.Automation.Language; using System.Management.Automation.Runspaces; using Dbg = System.Management.Automation.Diagnostics; @@ -15,16 +13,15 @@ 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. /// - internal class Executor { [Flags] @@ -47,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) { @@ -70,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. @@ -83,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 @@ -91,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. @@ -104,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) { @@ -150,7 +142,7 @@ private void PipelineStateChangedHandler(object sender, PipelineStateEventArgs e } } - private System.Threading.ManualResetEvent _eventHandle = new System.Threading.ManualResetEvent(false); + private readonly System.Threading.ManualResetEvent _eventHandle = new System.Threading.ManualResetEvent(false); } internal void ExecuteCommandAsync(string command, out Exception exceptionThrown, ExecutionOptions options) @@ -235,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; } @@ -300,52 +292,36 @@ 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); } - private Command GetOutDefaultCommand(bool endOfStatement) + private static Command GetOutDefaultCommand(bool endOfStatement) { return new Command(command: "Out-Default", isScript: false, @@ -464,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. /// /// @@ -505,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(); @@ -517,35 +492,32 @@ 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 /// objects were returned by the command. /// - 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. /// /// @@ -584,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() @@ -609,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(); @@ -623,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() { @@ -647,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 @@ -709,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() { @@ -721,24 +690,20 @@ 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 // to currentExecutor is guarded by staticStateLock, and static initializers are run by the CLR at program init time. private static Executor s_currentExecutor; - private static object s_staticStateLock = new object(); + private static readonly object s_staticStateLock = new object(); - private ConsoleHost _parent; + private readonly ConsoleHost _parent; private Pipeline _pipeline; private bool _cancelled; internal bool useNestedPipelines; - private object _instanceStateLock = new object(); - private bool _isPromptFunctionExecutor; + private readonly object _instanceStateLock = new object(); + private readonly bool _isPromptFunctionExecutor; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs index cfe7f0c16e1..acfdea07153 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.ComponentModel; using System.Globalization; @@ -14,35 +16,55 @@ 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. /// - public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)]string[] args, int argc) + [Obsolete("Callers should now use UnmanagedPSEntry.Start(string[], int)", error: true)] + public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2)] string[] args, int argc) { - // Warm up some components concurrently on background threads. - EarlyStartup.Init(); + return Start(args, argc); + } - // We need to read the settings file before we create the console host - CommandLineParameterParser.EarlyParse(args); + /// + /// Starts PowerShell. + /// + /// + /// 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) + { + ArgumentNullException.ThrowIfNull(args); -#if !UNIX - // NOTE: On Unix, logging has to be deferred until after command-line parsing - // complete. On Windows, deferring the call is not needed. - PSEtwLog.LogConsoleStartup(); +#if DEBUG + if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0]!.Equals("-isswait", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Attach the debugger to continue..."); + while (!System.Diagnostics.Debugger.IsAttached) + { + Thread.Sleep(100); + } + + System.Diagnostics.Debugger.Break(); + } #endif + // Warm up some components concurrently on background threads. + EarlyStartup.Init(); // Windows Vista and later support non-traditional UI fallback ie., a // user on an Arabic machine can choose either French or English(US) as @@ -54,34 +76,32 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray Thread.CurrentThread.CurrentUICulture = NativeCultureResolver.UICulture; Thread.CurrentThread.CurrentCulture = NativeCultureResolver.Culture; -#if DEBUG - if (args.Length > 0 && !string.IsNullOrEmpty(args[0]) && args[0].Equals("-isswait", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine("Attach the debugger to continue..."); - while (!System.Diagnostics.Debugger.IsAttached) - { - Thread.Sleep(100); - } + ConsoleHost.ParseCommandLine(args); + + // NOTE: On Unix, logging depends on a command line parsing + // and must be just after ConsoleHost.ParseCommandLine(args) + // to allow overriding logging options. + PSEtwLog.LogConsoleStartup(); - System.Diagnostics.Debugger.Break(); - } -#endif int exitCode = 0; try { - var banner = string.Format( + string banner = string.Format( CultureInfo.InvariantCulture, - ManagedEntranceStrings.ShellBannerNonWindowsPowerShell, + ManagedEntranceStrings.ShellBannerPowerShell, PSVersionInfo.GitCommitId); - exitCode = ConsoleShell.Start(banner, ManagedEntranceStrings.UsageHelp, args); + ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); + + exitCode = ConsoleHost.Start( + bannerText: banner, + helpText: ManagedEntranceStrings.UsageHelp, + issProvidedExternally: false); } catch (HostException e) { - if (e.InnerException != null && e.InnerException.GetType() == typeof(Win32Exception)) + if (e.InnerException is Win32Exception win32e) { - Win32Exception win32e = e.InnerException as Win32Exception; - // These exceptions are caused by killing conhost.exe // 1236, network connection aborted by local system // 0x6, invalid console handle @@ -102,4 +122,3 @@ public static int Start(string consoleFilePath, [MarshalAs(UnmanagedType.LPArray } } } - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs index ce4b6d95acb..b16fb5ee147 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/PendingProgress.cs @@ -24,7 +24,6 @@ namespace Microsoft.PowerShell /// This class uses lots of nearly identical helper functions to recursively traverse the tree. If I weren't so pressed /// for time, I would see if generic methods could be used to collapse the number of traversers. /// - internal class PendingProgress { @@ -42,10 +41,9 @@ class PendingProgress /// The ProgressRecord received that will either update the status of an activity which we are already tracking, or /// represent a new activity that we need to track. /// - internal void - Update(Int64 sourceId, ProgressRecord record) + Update(long sourceId, ProgressRecord record) { Dbg.Assert(record != null, "record should not be null"); @@ -121,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; @@ -179,7 +174,6 @@ class PendingProgress /// /// Index into the list of the node to be removed. /// - private void RemoveNode(ArrayList nodes, int indexToRemove) @@ -255,7 +249,6 @@ class PendingProgress /// /// Node to be added. /// - private void AddNode(ArrayList nodes, ProgressNode nodeToAdd) @@ -269,8 +262,7 @@ class PendingProgress #endif } - private - class FindOldestNodeVisitor : NodeVisitor + private sealed class FindOldestNodeVisitor : NodeVisitor { internal override bool @@ -302,6 +294,10 @@ internal override private int _oldestSoFar; } + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Accesses instance members in preprocessor branch.")] private ProgressNode FindOldestLeafmostNodeHelper(ArrayList treeToSearch, out ArrayList listWhereFound, out int indexWhereFound) @@ -337,7 +333,7 @@ internal override ProgressNode result = null; ArrayList treeToSearch = _topLevelNodes; - do + while (true) { result = FindOldestLeafmostNodeHelper(treeToSearch, out listWhereFound, out indexWhereFound); if (result == null || result.Children == null || result.Children.Count == 0) @@ -348,7 +344,7 @@ internal override // search the subtree for the oldest child treeToSearch = result.Children; - } while (true); + } return result; } @@ -356,10 +352,9 @@ internal override /// /// Convenience overload. /// - private ProgressNode - FindNodeById(Int64 sourceId, int activityId) + FindNodeById(long sourceId, int activityId) { ArrayList listWhereFound = null; int indexWhereFound = -1; @@ -367,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,8 +398,8 @@ internal override int IndexWhereFound = -1; - private int _idToFind = -1; - private Int64 _sourceIdToFind; + private readonly int _idToFind = -1; + private readonly long _sourceIdToFind; } /// @@ -427,10 +421,9 @@ internal override /// /// The found node, or null if no suitable node was located. /// - 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; @@ -465,10 +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) { @@ -511,15 +501,18 @@ internal override return found; } - private - class AgeAndResetStyleVisitor : NodeVisitor + private sealed class AgeAndResetStyleVisitor : NodeVisitor { internal override bool Visit(ProgressNode node, ArrayList unused, int unusedToo) { node.Age = Math.Min(node.Age + 1, Int32.MaxValue - 1); - node.Style = ProgressNode.RenderStyle.FullPlus; + + node.Style = ProgressNode.IsMinimalProgressRenderingEnabled() + ? ProgressNode.RenderStyle.Ansi + : node.Style = ProgressNode.RenderStyle.FullPlus; + return true; } } @@ -530,7 +523,6 @@ internal override /// /// All nodes are aged every time a new ProgressRecord is received. /// - private void AgeNodesAndResetStyle() @@ -561,7 +553,6 @@ internal override /// /// An array of strings containing the textual representation of the outstanding progress activities. /// - internal string[] Render(int maxWidth, int maxHeight, PSHostRawUserInterface rawUI) @@ -580,13 +571,20 @@ 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); } ArrayList result = new ArrayList(); + + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + RenderHelper(result, _topLevelNodes, indentation: 0, maxWidth, rawUI); + return (string[])result.ToArray(typeof(string)); + } + string border = StringUtil.Padding(maxWidth); result.Add(border); @@ -631,10 +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"); @@ -661,8 +656,7 @@ internal override } } - private - class HeightTallyer : NodeVisitor + private sealed class HeightTallyer : NodeVisitor { internal HeightTallyer(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) { @@ -686,9 +680,9 @@ internal override return true; } - private PSHostRawUserInterface _rawUi; - private int _maxHeight; - private int _maxWidth; + private readonly PSHostRawUserInterface _rawUi; + private readonly int _maxHeight; + private readonly int _maxWidth; internal int Tally; } @@ -708,7 +702,6 @@ internal override /// /// /// - private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) { HeightTallyer ht = new HeightTallyer(rawUi, maxHeight, maxWidth); @@ -724,10 +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) { @@ -759,7 +749,6 @@ private int TallyHeight(PSHostRawUserInterface rawUi, int maxHeight, int maxWidt /// /// Debugging code. NodeVisitor that counts up the number of nodes in the tree. /// - private class CountingNodeVisitor : NodeVisitor @@ -783,7 +772,6 @@ internal override /// /// The number of nodes in the tree. /// - private int CountNodes() @@ -823,7 +811,6 @@ internal override /// false to indicate that all of the nodes are compressed to a given level, but that the rendering still can't fit /// within the constraint. /// - private bool CompressToFitHelper( @@ -836,11 +823,9 @@ internal override { nodesCompressed = 0; - int age = 0; - - do + while (true) { - ProgressNode node = FindOldestNodeOfGivenStyle(_topLevelNodes, age, priorStyle); + ProgressNode node = FindOldestNodeOfGivenStyle(_topLevelNodes, oldestSoFar: 0, priorStyle); if (node == null) { // We've compressed every node of the prior style already. @@ -854,21 +839,9 @@ internal override { return true; } - } while (true); + } // 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; } @@ -893,8 +866,7 @@ internal override /// /// The number of nodes that were made invisible during the compression. /// - /// - + /// private int CompressToFit(PSHostRawUserInterface rawUi, int maxHeight, int maxWidth) @@ -956,8 +928,6 @@ internal override return nodesCompressed; } - Dbg.Assert(false, "with all nodes invisible, we should never reach this point."); - return 0; } @@ -983,7 +953,6 @@ class NodeVisitor /// /// true to continue visiting nodes, false if not. /// - internal abstract bool Visit(ProgressNode node, ArrayList listWhereFound, int indexWhereFound); @@ -1017,10 +986,9 @@ internal static #endregion - private ArrayList _topLevelNodes = new ArrayList(); + private readonly ArrayList _topLevelNodes = new ArrayList(); private int _nodeCount; private const int maxNodeCount = 128; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs index 35066c0bf09..4f51820524c 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressNode.cs @@ -6,6 +6,9 @@ using System.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; +using System.Text; + +using Microsoft.PowerShell.Commands.Internal.Format; using Dbg = System.Management.Automation.Diagnostics; @@ -15,15 +18,13 @@ namespace Microsoft.PowerShell /// ProgressNode is an augmentation of the ProgressRecord type that adds extra fields for the purposes of tracking /// outstanding activities received by the host, and rendering them in the console. /// - internal class ProgressNode : ProgressRecord { /// - /// Indicates the various layouts for rendering a particular node. Each style is progressively less terse. + /// Indicates the various layouts for rendering a particular node. /// - internal enum RenderStyle @@ -42,16 +43,19 @@ namespace Microsoft.PowerShell /// The node will be displayed the same as Full, plus, the whole StatusDescription and CurrentOperation will be displayed (in multiple lines if needed). /// FullPlus = 4, - }; + + /// + /// The node will be displayed using ANSI escape sequences. + /// + Ansi = 5, + } /// /// Constructs an instance from a ProgressRecord. /// - internal - ProgressNode(Int64 sourceId, ProgressRecord record) - : - base(record.ActivityId, record.Activity, record.StatusDescription) + 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"); @@ -60,7 +64,11 @@ namespace Microsoft.PowerShell this.PercentComplete = Math.Min(record.PercentComplete, 100); this.SecondsRemaining = record.SecondsRemaining; this.RecordType = record.RecordType; - this.Style = RenderStyle.FullPlus; + + this.Style = IsMinimalProgressRenderingEnabled() + ? RenderStyle.Ansi + : this.Style = RenderStyle.FullPlus; + this.SourceId = sourceId; } @@ -80,7 +88,6 @@ namespace Microsoft.PowerShell /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. /// - internal void Render(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -103,6 +110,9 @@ namespace Microsoft.PowerShell case RenderStyle.Minimal: RenderMinimal(strCollection, indentation, maxWidth, rawUI); break; + case RenderStyle.Ansi: + RenderAnsi(strCollection, indentation, maxWidth); + break; case RenderStyle.Invisible: // do nothing break; @@ -130,7 +140,6 @@ namespace Microsoft.PowerShell /// /// Indicate if the full StatusDescription and CurrentOperation should be displayed. /// - private void RenderFull(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI, bool isFullPlus) @@ -240,7 +249,6 @@ private static void RenderFullDescription(string description, string indent, int /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. /// - private void RenderCompact(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -309,7 +317,6 @@ private static void RenderFullDescription(string description, string indent, int /// /// The PSHostRawUserInterface used to gauge string widths in the rendering. /// - private void RenderMinimal(ArrayList strCollection, int indentation, int maxWidth, PSHostRawUserInterface rawUI) @@ -344,10 +351,111 @@ private static void RenderFullDescription(string description, string indent, int maxWidth)); } + internal static bool IsMinimalProgressRenderingEnabled() + { + return PSStyle.Instance.Progress.View == ProgressView.Minimal; + } + /// - /// The nodes that have this node as their parent. + /// Renders a node in the "ANSI" style. /// + /// + /// List of strings to which the node's rendering will be appended. + /// + /// + /// The indentation level in chars at which the node should be rendered. + /// + /// + /// The maximum number of chars that the rendering is allowed to consume. + /// + private + void + RenderAnsi(ArrayList strCollection, int indentation, int maxWidth) + { + string indent = StringUtil.Padding(indentation); + string secRemain = string.Empty; + if (SecondsRemaining >= 0) + { + secRemain = SecondsRemaining.ToString() + "s"; + } + + int secRemainLength = secRemain.Length + 1; + + // limit progress bar to 120 chars as no need to render full width + if (PSStyle.Instance.Progress.MaxWidth > 0 && maxWidth > PSStyle.Instance.Progress.MaxWidth) + { + 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; + + 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); + + int maxStatusLength = barWidth - secRemainLength - 1; + if (maxStatusLength > 0 && StatusDescription.Length > barWidth - secRemainLength) + { + sb.Append(StatusDescription.AsSpan(0, barWidth - secRemainLength - 1)); + sb.Append(PSObjectHelper.Ellipsis); + } + else + { + sb.Append(StatusDescription); + } + 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 && barWidth > 0) + { + int barLength = PercentComplete * barWidth / 100; + if (barLength >= barWidth) + { + barLength = barWidth - 1; + } + + if (barLength < sb.Length) + { + sb.Insert(barLength + PSStyle.Instance.Reverse.Length, PSStyle.Instance.ReverseOff); + } + } + else + { + sb.Append(PSStyle.Instance.ReverseOff); + } + + strCollection.Add( + StringUtil.Format( + "{0}{1}{2} [{3}]{4}", + indent, + PSStyle.Instance.Progress.Style, + activity, + sb.ToString(), + PSStyle.Instance.Reset) + .PadRight(padding)); + } + + /// + /// The nodes that have this node as their parent. + /// internal ArrayList Children; @@ -362,7 +470,6 @@ private static void RenderFullDescription(string description, string indent, int /// space. The rendering of nodes can be progressively "compressed" into a more terse format, or not rendered at all in /// order to fit as many nodes as possible in the available space. The oldest nodes are compressed or skipped first. /// - internal int Age; @@ -370,7 +477,6 @@ private static void RenderFullDescription(string description, string indent, int /// /// The style in which this node should be rendered. /// - internal RenderStyle Style = RenderStyle.FullPlus; @@ -378,16 +484,14 @@ private static void RenderFullDescription(string description, string indent, int /// /// Identifies the source of the progress record. /// - internal - Int64 + long SourceId; /// /// The number of vertical BufferCells that are required to render the node in its current style. /// /// - internal int LinesRequiredMethod(PSHostRawUserInterface rawUi, int maxWidth) { Dbg.Assert(this.RecordType != ProgressRecordType.Completed, "should never render completed records"); @@ -409,6 +513,9 @@ internal int LinesRequiredMethod(PSHostRawUserInterface rawUi, int maxWidth) case RenderStyle.Invisible: return 0; + case RenderStyle.Ansi: + return 1; + default: Dbg.Assert(false, "Unknown RenderStyle value"); break; @@ -421,7 +528,6 @@ internal int LinesRequiredMethod(PSHostRawUserInterface rawUi, int maxWidth) /// The number of vertical BufferCells that are required to render the node in the Full style. /// /// - private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int maxWidth, bool isFullPlus) { // Since the fields of this instance could have been changed, we compute this on-the-fly. @@ -479,7 +585,6 @@ private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int max /// The number of vertical BufferCells that are required to render the node in the Compact style. /// /// - private int LinesRequiredInCompactStyle @@ -504,4 +609,3 @@ private int LinesRequiredInFullStyleMethod(PSHostRawUserInterface rawUi, int max } } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs index 55dba38d6b4..030a359c2d8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ProgressPane.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Management.Automation; using System.Management.Automation.Host; using Dbg = System.Management.Automation.Diagnostics; @@ -12,8 +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 { @@ -23,11 +23,10 @@ class ProgressPane /// /// An implementation of the PSHostRawUserInterface with which the pane will be shown and hidden. /// - internal ProgressPane(ConsoleHostUserInterface ui) { - if (ui == null) throw new ArgumentNullException(nameof(ui)); + ArgumentNullException.ThrowIfNull(ui); _ui = ui; _rawui = ui.RawUI; } @@ -38,8 +37,7 @@ class ProgressPane /// /// true if the pane is visible, false if not. /// - /// - + /// internal bool IsShowing @@ -54,7 +52,6 @@ class ProgressPane /// Shows the pane in the screen buffer. Saves off the content of the region of the buffer that will be overwritten so /// that it can be restored again. /// - internal void Show() @@ -74,65 +71,89 @@ class ProgressPane int rows = tempProgressRegion.GetLength(0); int cols = tempProgressRegion.GetLength(1); + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + rows = _content.Length; + cols = PSStyle.Instance.Progress.MaxWidth; + if (cols > _bufSize.Width) + { + cols = _bufSize.Width; + } + } + _savedCursor = _rawui.CursorPosition; _location.X = 0; -#if UNIX - _location.Y = _rawui.CursorPosition.Y; - - // if cursor is not on left edge already move down one line - if (_rawui.CursorPosition.X != 0) + if (!Platform.IsWindows || ProgressNode.IsMinimalProgressRenderingEnabled()) { - _location.Y++; - _rawui.CursorPosition = _location; - } + _location.Y = _rawui.CursorPosition.Y; - // if the cursor is at the bottom, create screen buffer space by scrolling - int scrollRows = rows - ((_rawui.BufferSize.Height - 1) - _location.Y); - if (scrollRows > 0) - { - // Scroll the console screen up by 'scrollRows' - var bottomLocation = _location; - bottomLocation.Y = _rawui.BufferSize.Height; - _rawui.CursorPosition = bottomLocation; - for (int i = 0; i < scrollRows; i++) + // if cursor is not on left edge already move down one line + if (_rawui.CursorPosition.X != 0) { - Console.Out.Write('\n'); + _location.Y++; + _rawui.CursorPosition = _location; } - _location.Y -= scrollRows; - _savedCursor.Y -= scrollRows; - } + // if the cursor is at the bottom, create screen buffer space by scrolling + int scrollRows = rows - ((_rawui.BufferSize.Height - 1) - _location.Y); + if (scrollRows > 0) + { + // Scroll the console screen up by 'scrollRows' + var bottomLocation = _location; + bottomLocation.Y = _rawui.BufferSize.Height - 1; + + _rawui.CursorPosition = bottomLocation; + for (int i = 0; i < scrollRows; i++) + { + Console.Out.Write('\n'); + } + + _location.Y -= scrollRows; + _savedCursor.Y -= scrollRows; + } - // create cleared region to clear progress bar later - _savedRegion = tempProgressRegion; - for(int row = 0; row < rows; row++) - { - for(int col = 0; col < cols; col++) + // create cleared region to clear progress bar later + _savedRegion = tempProgressRegion; + if (PSStyle.Instance.Progress.View != ProgressView.Minimal) { - _savedRegion[row, col].Character = ' '; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + _savedRegion[row, col].Character = ' '; + } + } } - } - // put cursor back to where output should be - _rawui.CursorPosition = _location; -#else - _location = _rawui.WindowPosition; + // put cursor back to where output should be + _rawui.CursorPosition = _location; + } + else + { + _location = _rawui.WindowPosition; - // We have to show the progress pane in the first column, as the screen buffer at any point might contain - // a CJK double-cell characters, which makes it impractical to try to find a position where the pane would - // not slice a character. Column 0 is the only place where we know for sure we can place the pane. + // We have to show the progress pane in the first column, as the screen buffer at any point might contain + // a CJK double-cell characters, which makes it impractical to try to find a position where the pane would + // not slice a character. Column 0 is the only place where we know for sure we can place the pane. - _location.Y = Math.Min(_location.Y + 2, _bufSize.Height); + _location.Y = Math.Min(_location.Y + 2, _bufSize.Height); - // Save off the current contents of the screen buffer in the region that we will occupy - _savedRegion = - _rawui.GetBufferContents( - new Rectangle(_location.X, _location.Y, _location.X + cols - 1, _location.Y + rows - 1)); -#endif + // Save off the current contents of the screen buffer in the region that we will occupy + _savedRegion = + _rawui.GetBufferContents( + new Rectangle(_location.X, _location.Y, _location.X + cols - 1, _location.Y + rows - 1)); + } - // replace the saved region in the screen buffer with our progress display - _rawui.SetBufferContents(_location, tempProgressRegion); + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + WriteContent(); + } + else + { + // replace the saved region in the screen buffer with our progress display + _rawui.SetBufferContents(_location, tempProgressRegion); + } } } @@ -140,19 +161,43 @@ class ProgressPane /// Hides the pane by restoring the saved contents of the region of the buffer that the pane occupies. If the pane is /// not showing, then does nothing. /// - internal void Hide() { if (IsShowing) { - // It would be nice if we knew that the saved region could be kept for the next time Show is called, but alas, - // we have no way of knowing if the screen buffer has changed since we were hidden. By "no good way" I mean that - // detecting a change would be at least as expensive as chucking the savedRegion and rebuilding it. And it would - // be very complicated. + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + _rawui.CursorPosition = _location; + int maxWidth = PSStyle.Instance.Progress.MaxWidth; + if (maxWidth > _bufSize.Width) + { + maxWidth = _bufSize.Width; + } + + for (int i = 0; i < _savedRegion.GetLength(1); i++) + { + if (i < _savedRegion.GetLength(1) - 1) + { + Console.Out.WriteLine(string.Empty.PadRight(maxWidth)); + } + else + { + Console.Out.Write(string.Empty.PadRight(maxWidth)); + } + } + } + else + { + // It would be nice if we knew that the saved region could be kept for the next time Show is called, but alas, + // we have no way of knowing if the screen buffer has changed since we were hidden. By "no good way" I mean that + // detecting a change would be at least as expensive as chucking the savedRegion and rebuilding it. And it would + // be very complicated. + + _rawui.SetBufferContents(_location, _savedRegion); + } - _rawui.SetBufferContents(_location, _savedRegion); _savedRegion = null; _rawui.CursorPosition = _savedCursor; } @@ -164,7 +209,6 @@ class ProgressPane /// /// A PendingProgress instance that represents the outstanding activities that should be shown. /// - internal void Show(PendingProgress pendingProgress) @@ -179,8 +223,8 @@ class ProgressPane int maxWidth = _bufSize.Width; int maxHeight = Math.Max(5, _rawui.WindowSize.Height / 3); - string[] contents = pendingProgress.Render(maxWidth, maxHeight, _rawui); - if (contents == null) + _content = pendingProgress.Render(maxWidth, maxHeight, _rawui); + if (_content == null) { // There's nothing to show. @@ -189,9 +233,21 @@ class ProgressPane return; } - // NTRAID#Windows OS Bugs-1061752-2004/12/15-sburns should read a skin setting here... + BufferCell[,] newRegion; + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + // Legacy progress rendering relies on a BufferCell which defines a character, foreground color, and background color + // per cell. This model doesn't work with ANSI escape sequences. However, there is existing logic on rendering that + // relies on the existence of the BufferCell to know if something has been rendered previously. Here we are creating + // an empty BufferCell, but using the second dimension to capture the number of rows so that we can clear that many + // elsewhere in Hide(). + newRegion = new BufferCell[0, _content.Length]; + } + else + { + newRegion = _rawui.NewBufferCellArray(_content, _ui.ProgressForegroundColor, _ui.ProgressBackgroundColor); + } - BufferCell[,] newRegion = _rawui.NewBufferCellArray(contents, _ui.ProgressForegroundColor, _ui.ProgressBackgroundColor); Dbg.Assert(newRegion != null, "NewBufferCellArray has failed!"); if (_progressRegion == null) @@ -214,8 +270,7 @@ class ProgressPane bool sizeChanged = (newRegion.GetLength(0) != _progressRegion.GetLength(0)) - || (newRegion.GetLength(1) != _progressRegion.GetLength(1)) - ? true : false; + || (newRegion.GetLength(1) != _progressRegion.GetLength(1)); _progressRegion = newRegion; @@ -230,18 +285,65 @@ class ProgressPane } else { - _rawui.SetBufferContents(_location, _progressRegion); + if (ProgressNode.IsMinimalProgressRenderingEnabled()) + { + WriteContent(); + } + else + { + _rawui.SetBufferContents(_location, _progressRegion); + } } } } + 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; + + for (int i = 0; i < _content.Length; i++) + { + if (i < _content.Length - 1) + { + Console.Out.WriteLine(_content[i]); + } + else + { + Console.Out.Write(_content[i]); + } + } + + _rawui.CursorPosition = currentPosition; +#if UNIX + Console.CursorVisible = true; +#else + Console.CursorVisible = currentCursorVisible; +#endif + } + } + private Coordinates _location = new Coordinates(0, 0); private Coordinates _savedCursor; private Size _bufSize; private BufferCell[,] _savedRegion; private BufferCell[,] _progressRegion; - private PSHostRawUserInterface _rawui; - private ConsoleHostUserInterface _ui; + private string[] _content; + private readonly PSHostRawUserInterface _rawui; + private readonly ConsoleHostUserInterface _ui; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs index 696b95ca049..5181ae63672 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Serialization.cs @@ -14,26 +14,22 @@ namespace Microsoft.PowerShell /// Wraps Hitesh's xml serializer in such a way that it will select the proper serializer based on the data /// format. /// - internal class Serialization { /// /// Describes the format of the data streamed between minishells, e.g. the allowed arguments to the minishell /// -outputformat and -inputformat command line parameters. /// - internal enum DataFormat { /// /// Text format -- i.e. stream text just as out-default would display it. /// - Text = 0, /// /// XML-serialized format. /// - XML = 1, /// @@ -62,8 +58,7 @@ class WrappedSerializer : Serialization { internal WrappedSerializer(DataFormat dataFormat, string streamName, TextWriter output) - : - base(dataFormat, streamName) + : base(dataFormat, streamName) { Dbg.Assert(output != null, "output should have a value"); @@ -141,7 +136,7 @@ class WrappedSerializer : Serialization } internal TextWriter textWriter; - private XmlWriter _xmlWriter; + private readonly XmlWriter _xmlWriter; private Serializer _xmlSerializer; private bool _firstCall = true; } @@ -151,8 +146,7 @@ class WrappedDeserializer : Serialization { internal WrappedDeserializer(DataFormat dataFormat, string streamName, TextReader input) - : - base(dataFormat, streamName) + : base(dataFormat, streamName) { Dbg.Assert(input != null, "input should have a value"); @@ -195,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: @@ -271,10 +264,9 @@ class WrappedDeserializer : Serialization } internal TextReader textReader; - private XmlReader _xmlReader; - private Deserializer _xmlDeserializer; + private readonly XmlReader _xmlReader; + private readonly Deserializer _xmlDeserializer; private string _firstLine; private bool _atEnd; } } // namespace - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs index 1587f91d3f9..18725b5ddb7 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs @@ -12,7 +12,6 @@ namespace Microsoft.PowerShell.Commands /// /// Implements the start-transcript cmdlet. /// - [Cmdlet(VerbsLifecycle.Start, "Transcript", SupportsShouldProcess = true, DefaultParameterSetName = "ByPath", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096485")] [OutputType(typeof(string))] public sealed class StartTranscriptCommand : PSCmdlet @@ -23,7 +22,6 @@ public sealed class StartTranscriptCommand : PSCmdlet /// Documents/PowerShell_transcript.YYYYMMDDmmss.txt. /// /// - [Parameter(Position = 0, ParameterSetName = "ByPath")] [ValidateNotNullOrEmpty] public string Path @@ -77,7 +75,6 @@ public string OutputDirectory /// Describes the current state of the activity. /// /// - [Parameter] public SwitchParameter Append { @@ -99,8 +96,7 @@ public SwitchParameter Append /// /// The read-only attribute will not be replaced when the transcript is done. /// - - [Parameter()] + [Parameter] public SwitchParameter Force { get @@ -119,7 +115,7 @@ public SwitchParameter Force /// /// Property that prevents file overwrite. /// - [Parameter()] + [Parameter] [Alias("NoOverwrite")] public SwitchParameter NoClobber { @@ -139,7 +135,7 @@ public SwitchParameter NoClobber /// /// Whether to include command invocation time headers between commands. /// - [Parameter()] + [Parameter] public SwitchParameter IncludeInvocationHeader { get; set; @@ -181,7 +177,7 @@ protected override void BeginProcessing() } else { - _outFilename = (string)value; + _outFilename = (string)PSObject.Base(value); } } @@ -350,4 +346,3 @@ private void ReportMultipleFilesNotSupported() private bool _isFilenameSet; } } - diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs index 9fae35ac83e..093f7c147dc 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs @@ -10,19 +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 5c202eb32b1..eb4557c04d2 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -28,6 +28,9 @@ internal static class UpdatesNotification private const string StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable"; private const string PreviewBuildInfoURL = "https://aka.ms/pwsh-buildinfo-preview"; + private const int NotificationDelayDays = 7; + private const int UpdateCheckBackoffDays = 7; + /// /// The version of new update is persisted using a file, not as the file content, but instead baked in the file name in the following template: /// `update{notification-type}_{version}_{publish-date}` -- held by 's_updateFileNameTemplate', @@ -57,12 +60,12 @@ internal static class UpdatesNotification static UpdatesNotification() { s_notificationType = GetNotificationType(); - CanNotifyUpdates = s_notificationType != NotificationType.Off; + CanNotifyUpdates = s_notificationType != NotificationType.Off + && Platform.TryDeriveFromCache(PSVersionInfo.GitCommitId, out s_cacheDirectory); if (CanNotifyUpdates) { s_enumOptions = new EnumerationOptions(); - s_cacheDirectory = Path.Combine(Platform.CacheDirectory, PSVersionInfo.GitCommitId); // Build the template/pattern strings for the configured notification type. string typeNum = ((int)s_notificationType).ToString(); @@ -89,9 +92,18 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) if (TryParseUpdateFile( updateFilePath: out _, out SemanticVersion lastUpdateVersion, - lastUpdateDate: out _) + out DateTime lastUpdateDate) && lastUpdateVersion != null) { + DateTime today = DateTime.UtcNow; + if ((today - lastUpdateDate).TotalDays < NotificationDelayDays) + { + // The update was out less than 1 week ago and it's possible the packages are still rolling out. + // We only show the notification when the update is at least 1 week old, to reduce the chance that + // users see the notification but cannot get the new update when they try to install it. + return; + } + string releaseTag = lastUpdateVersion.ToString(); string notificationMsgTemplate = s_notificationType == NotificationType.LTS ? ManagedEntranceStrings.LTSUpdateNotificationMessage @@ -108,7 +120,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 +138,7 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) string notificationMsg = string.Format(CultureInfo.CurrentCulture, notificationMsgTemplate, releaseTag, notificationColor, resetColor, line2Padding, line3Padding); + hostUI.WriteLine(); hostUI.WriteLine(notificationMsg); } } @@ -168,7 +181,7 @@ internal static async Task CheckForUpdates() out DateTime lastUpdateDate); DateTime today = DateTime.UtcNow; - if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < 7) + if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < UpdateCheckBackoffDays) { // There is an existing update file, and the last update was less than 1 week ago. // It's unlikely a new version is released within 1 week, so we can skip this check. @@ -315,7 +328,7 @@ private static bool TryParseUpdateFile( int dateStartIndex = updateFileName.LastIndexOf('_') + 1; if (!DateTime.TryParse( - updateFileName.AsSpan().Slice(dateStartIndex), + updateFileName.AsSpan(dateStartIndex), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out lastUpdateDate)) @@ -352,7 +365,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 +424,7 @@ private static NotificationType GetNotificationType() private enum NotificationType { /// - /// Turn off the udpate notification. + /// Turn off the update notification. /// Off = 0, @@ -428,7 +441,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 9703420c254..33445ceebd2 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. @@ -219,4 +219,13 @@ Valid formats are: Parameter -STA is not supported on this platform. + + The specified arguments must not contain null elements. + + + Invalid ExecutionPolicy value '{0}'. + + + An argument is required to be supplied to the '{0}' parameter. + 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..9bc06e0d42f 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,10 @@ The current session does not support debugging; execution will continue. Run as Administrator + + PushRunspace can only push a remote runspace. + + + The '{0}' parameter is mandatory and must be specified when using the '{1}' parameter. + diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index 45f387df28b..44ebf0fb538 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -117,15 +117,20 @@ 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] - - 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'. + + [No Language Mode] + + + [Restricted Language Mode] {1} A new PowerShell preview release is available: v{0} {2} @@ -149,12 +154,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>] [-STA] [-Version] - [-WindowStyle <style>] [-WorkingDirectory <directoryPath>] + [-NoProfileLoadTime] [-OutputFormat {Text | XML}] + [-SettingsFile <filePath>] [-SSHServerMode] [-STA] + [-Version] [-WindowStyle <style>] + [-WorkingDirectory <directoryPath>] pwsh[.exe] -h | -Help | -? | /? @@ -183,10 +191,11 @@ All parameters are case-insensitive. omitted. For example, the following command uses the All parameter of the Get-Script.ps1 script file: "-File .\Get-Script.ps1 -All" - In rare cases, you might need to provide a Boolean value for a switch - parameter. To provide a Boolean value for a switch parameter in the value - of the File parameter, enclose the parameter name and value in curly - braces, such as the following: "-File .\Get-Script.ps1 {-All:$False}." + In rare cases, you might need to provide a BOOLEAN value for a switch + parameter. To provide a BOOLEAN value for a switch parameter in the value + of the FILE parameter, Use the parameter normally followed immediately by a + colon and the boolean value, such as the following: + "-File .\Get-Script.ps1 -All:$False". Parameters passed to the script are passed as literal strings, after interpretation by the current shell. For example, if you are in cmd.exe and @@ -197,7 +206,21 @@ All parameters are case-insensitive. cmd.exe results in the script receiving the literal string "$env:windir" because it has no special meaning to the current cmd.exe shell. The "$env:windir" style of environment variable reference can be used inside a - Command parameter, since there it will be interpreted as PowerShell code. + Command parameter, since there it is interpreted as PowerShell code. + + Similarly, if you want to execute the same command from a Batch script, + you would use "%~dp0" instead of ".\" or "$PSScriptRoot" to represent the current + execution directory: "pwsh -File %~dp0test.ps1 -TestParam %windir%". If you + instead used ".\test.ps1", PowerShell would throw an error because it cannot + find the literal path ".\test.ps1". + + When the script file invoked terminates with an exit command, the process + exit code is set to the numeric argument used with the exit command. With + normal termination, the exit code is always 0. + + Similar to -Command, when a script-terminating error occurs, the exit code + is set to 1. However, unlike with -Command, when the execution is + interrupted with Ctrl-C the exit code is 0. -Command | -c @@ -215,8 +238,7 @@ All parameters are case-insensitive. or parsed by the PowerShell host as a literal script block enclosed in curly braces "{}", before being passed to pwsh. - - pwsh -Command {Get-WinEvent -LogName security} + pwsh -Command {Get-WinEvent -LogName security} In cmd.exe, there is no such thing as a script block (or ScriptBlock type), so the value passed to Command will always be a string. You can write a @@ -224,7 +246,7 @@ All parameters are case-insensitive. behave exactly as though you typed it at a typical PowerShell prompt, printing the contents of the script block back out to you. - A string passed to Command will still be executed as PowerShell, so the + A string passed to Command is still executed as PowerShell script, so the script block curly braces are often not required in the first place when running from cmd.exe. To execute an inline script block defined inside a string, the call operator "&" can be used: @@ -235,29 +257,58 @@ All parameters are case-insensitive. pwsh, because all arguments following it are interpreted as part of the command to execute. - The results are returned to the parent shell as deserialized XML objects, - not live objects. + When called from within an existing PowerShell session, the results are + returned to the parent shell as deserialized XML objects, not live objects. + For other shells, the results are returned as strings. If the value of Command is "-", the command text is read from standard input. You must redirect standard input when using the Command parameter with standard input. For example: + @' + "in" + + "hi" | + % { "$_ there" } - @' - "in" + "out" + '@ | powershell -NoProfile -Command - - "hi" | - % { "$_ there" } + This example produces the following output: + + in + hi there + out + + The process exit code is determined by status of the last (executed) + command within the script block. The exit code is 0 when $? is $true or 1 + when $? is $false. If the last command is an external program or a + PowerShell script that explicitly sets an exit code other than 0 or 1, that + exit code is converted to 1 for process exit code. To preserve the specific + exit code, add exit $LASTEXITCODE to your command string or script block. + + Similarly, the value 1 is returned when a script-terminating + (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: - "out" - '@ | powershell -NoProfile -Command - + pwsh -CommandWithArgs '$args | % { "arg: $_" }' arg1 arg2 This example produces the following output: - """Output - in - hi there - out + arg: arg1 + arg: arg2 -ConfigurationName | -config @@ -268,6 +319,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 @@ -275,36 +334,39 @@ All parameters are case-insensitive. predictable mechanism for connecting to other PowerShell instances. Typically used with the CustomPipeName parameter on "Enter-PSHostProcess". - For example: + This parameter was introduced in PowerShell 6.2. + For example: - # PowerShell instance 1 - pwsh -CustomPipeName mydebugpipe - # PowerShell instance 2 - Enter-PSHostProcess -CustomPipeName mydebugpipe + # PowerShell instance 1 + pwsh -CustomPipeName mydebugpipe + # PowerShell instance 2 + Enter-PSHostProcess -CustomPipeName mydebugpipe -EncodedCommand | -e | -ec - Accepts a base64-encoded string version of a command. Use this parameter to - submit commands to PowerShell that require complex quotation marks or curly - braces. The string must be formatted using UTF-16 character encoding. + Accepts a Base64-encoded string version of a command. Use this parameter to + submit commands to PowerShell that require complex, nested quoting. The + Base64 representation must be a UTF-16 encoded string. For example: - - $command = 'dir "c:\program files" ' - $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) - $encodedCommand = [Convert]::ToBase64String($bytes) - pwsh -encodedcommand $encodedCommand + $command = 'dir "c:\program files" ' + $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) + $encodedCommand = [Convert]::ToBase64String($bytes) + pwsh -encodedcommand $encodedCommand -ExecutionPolicy | -ex | -ep Sets the default execution policy for the current session and saves it in - the "$env:PSExecutionPolicyPreference" environment variable. This parameter - does not change the PowerShell execution policy that is set in the - registry. + the $env:PSExecutionPolicyPreference environment variable. This parameter + does not change the persistently configured execution policies. + + This parameter only applies to Windows computers. The + $env:PSExecutionPolicyPreference environment variable does not exist on + non-Windows platforms. --InputFormat | -in | -if +-InputFormat | -inp | -if Describes the format of data sent to PowerShell. Valid values are "Text" (text strings) or "XML" (serialized CLIXML format). @@ -316,16 +378,37 @@ All parameters are case-insensitive. -Login | -l - On Linux and macOS, starts PowerShell as a login shell, - using /bin/sh to execute login profiles such as /etc/profile and ~/.profile. - On Windows, this switch does nothing. + On Linux and macOS, starts PowerShell as a login shell, using /bin/sh to + execute login profiles such as /etc/profile and ~/.profile. On Windows, + this switch does nothing. + + [!IMPORTANT] This parameter must come first to start PowerShell as a login + shell. The parameter is ignored if passed in any other position. + + To set up pwsh as the login shell on UNIX-like operating systems: - Note that "-Login" is only supported as the first parameter to pwsh. + - Verify that the full absolute path to pwsh is listed under /etc/shells + + - This path is usually something like /usr/bin/pwsh on Linux or + /usr/local/bin/pwsh on macOS + - With some installation methods, this entry will be added + automatically at installation time + - If pwsh is not present in /etc/shells, use an editor to append the + path to pwsh on the last line. This requires elevated privileges to + edit. + + - Use the chsh utility to set your current user's shell to pwsh: + + chsh -s /usr/bin/pwsh + + [!WARNING] Setting pwsh as the login shell is currently not supported on + Windows Subsystem for Linux (WSL), and attempting to set pwsh as the + login shell there may lead to being unable to start WSL interactively. -MTA - Start the shell using a multi-threaded apartment. - Only available on Windows. + Start PowerShell using a multi-threaded apartment. This switch is only + available on Windows. -NoExit | -noe @@ -335,15 +418,23 @@ All parameters are case-insensitive. -NoLogo | -nol -Hides the copyright banner at startup. + Hides the banner text at startup of interactive sessions. -NonInteractive | -noni - Does not present an interactive prompt to the user. + 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 profile. + 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 @@ -352,6 +443,10 @@ Hides the copyright banner at startup. Example: "pwsh -o XML -c Get-Date" + 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. + -SettingsFile | -settings Overrides the system-wide "powershell.config.json" settings file for the @@ -363,10 +458,15 @@ Hides the copyright banner at startup. Example: "pwsh -SettingsFile c:\myproject\powershell.config.json" +-SSHServerMode | -sshs + + Used in sshd_config for running PowerShell as an SSH subsystem. It is not + intended or supported for any other use. + -STA - Start the shell using a single-threaded apartment. This is the default. - Only available on Windows. + Start PowerShell using a single-threaded apartment. This is the default. + This switch is only available on Windows. -Version | -v @@ -375,21 +475,19 @@ Hides the copyright banner at startup. -WindowStyle | -w Sets the window style for the session. Valid values are Normal, Minimized, - Maximized and Hidden. + Maximized, and Hidden. -WorkingDirectory | -wd - Sets the initial working directory by executing - "Set-Location -LiteralPath <path>" at startup. Any valid PowerShell file - path is supported. + Sets the initial working directory by executing at startup. Any valid + PowerShell file path is supported. - To start PowerShell in your home directory, use: "pwsh -WorkingDirectory ~" + To start PowerShell in your home directory, use: pwsh -WorkingDirectory ~ -Help, -?, /? Displays help for pwsh. If you are typing a pwsh command in PowerShell, prepend the command parameters with a hyphen (-), not a forward slash (/). - You can use either a hyphen or forward slash in Cmd.exe. 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/AssemblyInfo.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/AssemblyInfo.cs index 4d72090e088..2a5b6533d18 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/AssemblyInfo.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/AssemblyInfo.cs @@ -1,6 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Reflection; using System.Runtime.CompilerServices; + [assembly: InternalsVisibleTo("System.Management.Automation,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventDescriptor.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventDescriptor.cs index 34fa9105085..723832979f8 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventDescriptor.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventDescriptor.cs @@ -8,28 +8,28 @@ namespace System.Diagnostics.Eventing { [StructLayout(LayoutKind.Explicit, Size = 16)] - public struct EventDescriptor + public readonly struct EventDescriptor { [FieldOffset(0)] - private ushort _id; + private readonly ushort _id; [FieldOffset(2)] - private byte _version; + private readonly byte _version; [FieldOffset(3)] - private byte _channel; + private readonly byte _channel; [FieldOffset(4)] - private byte _level; + private readonly byte _level; [FieldOffset(5)] - private byte _opcode; + private readonly byte _opcode; [FieldOffset(6)] - private ushort _task; + private readonly ushort _task; [FieldOffset(8)] - private long _keywords; + private readonly long _keywords; [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "opcode", Justification = "matell: Shipped public in 3.5, breaking change to fix now.")] public EventDescriptor( diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs index a1eba143e28..32ce9993cfb 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProvider.cs @@ -20,7 +20,7 @@ public class EventProvider : IDisposable private long _anyKeywordMask; // Trace Enable Flags private long _allKeywordMask; // Match all keyword private int _enabled; // Enabled flag from Trace callback - private Guid _providerId; // Control Guid + private readonly Guid _providerId; // Control Guid private int _disposed; // when 1, provider has unregister [ThreadStatic] @@ -99,7 +99,7 @@ private unsafe void EtwRegister() _etwCallback = new UnsafeNativeMethods.EtwEnableCallback(EtwEnableCallBack); - status = UnsafeNativeMethods.EventRegister(ref _providerId, _etwCallback, null, ref _regHandle); + status = UnsafeNativeMethods.EventRegister(in _providerId, _etwCallback, null, ref _regHandle); if (status != 0) { throw new Win32Exception((int)status); @@ -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) { @@ -202,7 +205,7 @@ [In] void* callbackContext /// public bool IsEnabled() { - return (_enabled != 0) ? true : false; + return _enabled != 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)) { @@ -488,9 +487,9 @@ public bool WriteMessageEvent(string eventMessage) /// /// /// - public bool WriteEvent(ref EventDescriptor eventDescriptor, params object[] eventPayload) + public bool WriteEvent(in EventDescriptor eventDescriptor, params object[] eventPayload) { - return WriteTransferEvent(ref eventDescriptor, Guid.Empty, eventPayload); + return WriteTransferEvent(in eventDescriptor, Guid.Empty, eventPayload); } /// @@ -504,14 +503,11 @@ public bool WriteEvent(ref EventDescriptor eventDescriptor, params object[] even /// [System.Security.SecurityCritical] [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] - public bool WriteEvent(ref EventDescriptor eventDescriptor, string data) + 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)) { @@ -534,7 +530,7 @@ public bool WriteEvent(ref EventDescriptor eventDescriptor, string data) userData.DataPointer = (ulong)pdata; status = UnsafeNativeMethods.EventWriteTransfer(_regHandle, - ref eventDescriptor, + in eventDescriptor, (activityId == Guid.Empty) ? null : &activityId, null, 1, @@ -565,7 +561,7 @@ public bool WriteEvent(ref EventDescriptor eventDescriptor, string data) /// pointer do the event data /// [System.Security.SecurityCritical] - protected bool WriteEvent(ref EventDescriptor eventDescriptor, int dataCount, IntPtr data) + protected bool WriteEvent(in EventDescriptor eventDescriptor, int dataCount, IntPtr data) { uint status = 0; @@ -575,7 +571,7 @@ protected bool WriteEvent(ref EventDescriptor eventDescriptor, int dataCount, In status = UnsafeNativeMethods.EventWriteTransfer( _regHandle, - ref eventDescriptor, + in eventDescriptor, (activityId == Guid.Empty) ? null : &activityId, null, (uint)dataCount, @@ -602,7 +598,7 @@ protected bool WriteEvent(ref EventDescriptor eventDescriptor, int dataCount, In /// /// [System.Security.SecurityCritical] - public bool WriteTransferEvent(ref EventDescriptor eventDescriptor, Guid relatedActivityId, params object[] eventPayload) + public bool WriteTransferEvent(in EventDescriptor eventDescriptor, Guid relatedActivityId, params object[] eventPayload) { uint status = 0; @@ -719,7 +715,7 @@ public bool WriteTransferEvent(ref EventDescriptor eventDescriptor, Guid related } status = UnsafeNativeMethods.EventWriteTransfer(_regHandle, - ref eventDescriptor, + in eventDescriptor, (activityId == Guid.Empty) ? null : &activityId, (relatedActivityId == Guid.Empty) ? null : &relatedActivityId, (uint)argCount, @@ -737,7 +733,7 @@ public bool WriteTransferEvent(ref EventDescriptor eventDescriptor, Guid related } [System.Security.SecurityCritical] - protected bool WriteTransferEvent(ref EventDescriptor eventDescriptor, Guid relatedActivityId, int dataCount, IntPtr data) + protected bool WriteTransferEvent(in EventDescriptor eventDescriptor, Guid relatedActivityId, int dataCount, IntPtr data) { uint status = 0; @@ -747,7 +743,7 @@ protected bool WriteTransferEvent(ref EventDescriptor eventDescriptor, Guid rela { status = UnsafeNativeMethods.EventWriteTransfer( _regHandle, - ref eventDescriptor, + in eventDescriptor, (activityId == Guid.Empty) ? null : &activityId, &relatedActivityId, (uint)dataCount, @@ -779,7 +775,7 @@ public static void SetActivityId(ref Guid id) [System.Security.SecurityCritical] public static Guid CreateActivityId() { - Guid newId = new Guid(); + Guid newId = new(); UnsafeNativeMethods.EventActivityIdControl((int)ActivityControl.EVENT_ACTIVITY_CTRL_CREATE_ID, ref newId); return newId; } diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/EventProviderTraceListener.cs index 97c10d167ef..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); @@ -84,7 +82,7 @@ public EventProviderTraceListener(string providerId, string name, string delimit private void InitProvider(string providerId) { - Guid controlGuid = new Guid(providerId); + Guid controlGuid = new(providerId); // // Create The ETW TraceProvider // @@ -148,7 +146,7 @@ public sealed override void TraceData(TraceEventCache eventCache, string source, return; } - StringBuilder dataString = new StringBuilder(s_defaultPayloadSize); + StringBuilder dataString = new(s_defaultPayloadSize); if (data != null) { @@ -177,7 +175,7 @@ public sealed override void TraceData(TraceEventCache eventCache, string source, } int index; - StringBuilder dataString = new StringBuilder(s_defaultPayloadSize); + StringBuilder dataString = new(s_defaultPayloadSize); if ((data != null) && (data.Length > 0)) { @@ -242,7 +240,7 @@ public sealed override void TraceEvent(TraceEventCache eventCache, string source return; } - StringBuilder dataString = new StringBuilder(s_defaultPayloadSize); + StringBuilder dataString = new(s_defaultPayloadSize); dataString.Append(message); _provider.WriteMessageEvent(dataString.ToString(), @@ -278,10 +276,10 @@ public sealed override void TraceEvent(TraceEventCache eventCache, string source public override void Fail(string message, string detailMessage) { - StringBuilder failMessage = new StringBuilder(message); + StringBuilder failMessage = new(message); if (detailMessage != null) { - failMessage.Append(" "); + failMessage.Append(' '); failMessage.Append(detailMessage); } 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 8858b067f8a..df2b064ebd5 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/Reader/NativeWrapper.cs @@ -17,12 +17,12 @@ using System.Collections.Generic; using System.ComponentModel; using System.Runtime.InteropServices; -using System.Text; using System.Security.Principal; +using System.Text; namespace System.Diagnostics.Eventing.Reader { - internal class NativeWrapper + internal static class NativeWrapper { public class SystemProperties { @@ -307,7 +307,7 @@ public static void EvtClearLog( [System.Security.SecurityCritical] public static EventLogHandle EvtCreateRenderContext( - Int32 valuePathsCount, + int valuePathsCount, string[] valuePaths, UnsafeNativeMethods.EvtRenderContextFlags flags) { @@ -514,7 +514,7 @@ public static string EvtFormatMessage(EventLogHandle handle, uint msgId) { int bufferNeeded; - StringBuilder sb = new StringBuilder(null); + StringBuilder sb = new(null); bool status = UnsafeNativeMethods.EvtFormatMessage(handle, EventLogHandle.Zero, msgId, 0, null, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageId, 0, sb, out bufferNeeded); int error = Marshal.GetLastWin32Error(); @@ -672,9 +672,9 @@ public static object EvtGetChannelConfigProperty(EventLogHandle handle, UnsafeNa [System.Security.SecuritySafeCritical] public static void EvtSetChannelConfigProperty(EventLogHandle handle, UnsafeNativeMethods.EvtChannelConfigPropertyId enumType, object val) { - UnsafeNativeMethods.EvtVariant varVal = new UnsafeNativeMethods.EvtVariant(); + UnsafeNativeMethods.EvtVariant varVal = new(); - CoTaskMemSafeHandle taskMem = new CoTaskMemSafeHandle(); + CoTaskMemSafeHandle taskMem = new(); using (taskMem) { @@ -762,7 +762,7 @@ public static void EvtSetChannelConfigProperty(EventLogHandle handle, UnsafeNati [System.Security.SecurityCritical] public static string EvtNextChannelPath(EventLogHandle handle, ref bool finish) { - StringBuilder sb = new StringBuilder(null); + StringBuilder sb = new(null); int channelNameNeeded; bool status = UnsafeNativeMethods.EvtNextChannelPath(handle, 0, sb, out channelNameNeeded); @@ -791,7 +791,7 @@ public static string EvtNextChannelPath(EventLogHandle handle, ref bool finish) [System.Security.SecurityCritical] public static string EvtNextPublisherId(EventLogHandle handle, ref bool finish) { - StringBuilder sb = new StringBuilder(null); + StringBuilder sb = new(null); int ProviderIdNeeded; bool status = UnsafeNativeMethods.EvtNextPublisherId(handle, 0, sb, out ProviderIdNeeded); @@ -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 @@ -958,7 +958,7 @@ public static IList EvtRenderBufferWithContextUserOrValues(EventLogHandl IntPtr pointer = IntPtr.Zero; int bufferNeeded; int propCount; - UnsafeNativeMethods.EvtRenderFlags flag = UnsafeNativeMethods.EvtRenderFlags.EvtRenderEventValues; + const UnsafeNativeMethods.EvtRenderFlags flag = UnsafeNativeMethods.EvtRenderFlags.EvtRenderEventValues; try { @@ -976,7 +976,7 @@ public static IList EvtRenderBufferWithContextUserOrValues(EventLogHandl if (!status) ThrowEventLogException(win32Error); - List valuesList = new List(propCount); + List valuesList = new(propCount); if (propCount > 0) { pointer = buffer; @@ -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))); } } @@ -1001,7 +1001,7 @@ public static IList EvtRenderBufferWithContextUserOrValues(EventLogHandl public static string EvtFormatMessageRenderName(EventLogHandle pmHandle, EventLogHandle eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags flag) { int bufferNeeded; - StringBuilder sb = new StringBuilder(null); + StringBuilder sb = new(null); bool status = UnsafeNativeMethods.EvtFormatMessage(pmHandle, eventHandle, 0, 0, null, flag, 0, sb, out bufferNeeded); int error = Marshal.GetLastWin32Error(); @@ -1059,7 +1059,7 @@ public static IEnumerable EvtFormatMessageRenderKeywords(EventLogHandle try { - List keywordsList = new List(); + List keywordsList = new(); bool status = UnsafeNativeMethods.EvtFormatMessageBuffer(pmHandle, eventHandle, 0, 0, IntPtr.Zero, flag, 0, IntPtr.Zero, out bufferNeeded); int error = Marshal.GetLastWin32Error(); @@ -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(); @@ -1124,7 +1124,7 @@ public static string EvtRenderBookmark(EventLogHandle eventHandle) IntPtr buffer = IntPtr.Zero; int bufferNeeded; int propCount; - UnsafeNativeMethods.EvtRenderFlags flag = UnsafeNativeMethods.EvtRenderFlags.EvtRenderBookmark; + const UnsafeNativeMethods.EvtRenderFlags flag = UnsafeNativeMethods.EvtRenderFlags.EvtRenderBookmark; try { @@ -1164,7 +1164,7 @@ public static string EvtFormatMessageFormatDescription(EventLogHandle handle, Ev stringVariants[i].StringVal = values[i]; } - StringBuilder sb = new StringBuilder(null); + StringBuilder sb = new(null); bool status = UnsafeNativeMethods.EvtFormatMessage(handle, eventHandle, 0xffffffff, values.Length, stringVariants, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageEvent, 0, sb, out bufferNeeded); int error = Marshal.GetLastWin32Error(); @@ -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; @@ -1396,9 +1396,9 @@ public static Array ConvertToBoolArray(UnsafeNativeMethods.EvtVariant val) bool[] array = new bool[val.Count]; for (int i = 0; i < val.Count; i++) { - bool value = (Marshal.ReadInt32(ptr) != 0) ? true : false; + 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 5da8809897d..b7beee133ed 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/DotNetCode/Eventing/UnsafeNativeMethods.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Diagnostics.Eventing.Reader; using System.Runtime.InteropServices; using System.Security; @@ -85,7 +84,7 @@ internal static extern int FormatMessage(int dwFlags, IntPtr lpSource, [SecurityCritical] internal static string GetMessage(int errorCode) { - StringBuilder sb = new StringBuilder(512); + StringBuilder sb = new(512); int result = UnsafeNativeMethods.FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY, UnsafeNativeMethods.s_NULL, errorCode, 0, sb, sb.Capacity, UnsafeNativeMethods.s_NULL); @@ -120,10 +119,10 @@ [In] void* callbackContext [DllImport(EventProviderDllName, ExactSpelling = true, EntryPoint = "EventRegister", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] [SecurityCritical] internal static extern unsafe uint EventRegister( - [In] ref Guid providerId, - [In]EtwEnableCallback enableCallback, - [In]void* callbackContext, - [In][Out]ref long registrationHandle + [In] in Guid providerId, + [In] EtwEnableCallback enableCallback, + [In] void* callbackContext, + [In][Out] ref long registrationHandle ); [DllImport(EventProviderDllName, ExactSpelling = true, EntryPoint = "EventUnregister", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] @@ -133,7 +132,7 @@ [In][Out]ref long registrationHandle // Control (Is Enabled) APIs [DllImport(EventProviderDllName, ExactSpelling = true, EntryPoint = "EventEnabled", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] [SecurityCritical] - internal static extern int EventEnabled([In] long registrationHandle, [In] ref System.Diagnostics.Eventing.EventDescriptor eventDescriptor); + internal static extern int EventEnabled([In] long registrationHandle, [In] in System.Diagnostics.Eventing.EventDescriptor eventDescriptor); [DllImport(EventProviderDllName, ExactSpelling = true, EntryPoint = "EventProviderEnabled", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] [SecurityCritical] @@ -144,7 +143,7 @@ [In][Out]ref long registrationHandle [SecurityCritical] internal static extern unsafe uint EventWrite( [In] long registrationHandle, - [In] ref EventDescriptor eventDescriptor, + [In] in EventDescriptor eventDescriptor, [In] uint userDataCount, [In] void* userData ); @@ -163,7 +162,7 @@ [In] void* userData [SecurityCritical] internal static extern unsafe uint EventWriteTransfer( [In] long registrationHandle, - [In] ref EventDescriptor eventDescriptor, + [In] in EventDescriptor eventDescriptor, [In] Guid* activityId, [In] Guid* relatedActivityId, [In] uint userDataCount, @@ -178,6 +177,7 @@ internal static extern unsafe uint EventWriteString( [In] long keywords, [In] char* message ); + // ActivityId Control APIs [DllImport(EventProviderDllName, ExactSpelling = true, EntryPoint = "EventActivityIdControl", CharSet = System.Runtime.InteropServices.CharSet.Unicode)] [SecurityCritical] @@ -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 } @@ -553,8 +553,8 @@ internal enum EvtSeekFlags [SecurityCritical] internal static extern EventLogHandle EvtQuery( EventLogHandle session, - [MarshalAs(UnmanagedType.LPWStr)]string path, - [MarshalAs(UnmanagedType.LPWStr)]string query, + [MarshalAs(UnmanagedType.LPWStr)] string path, + [MarshalAs(UnmanagedType.LPWStr)] string query, int flags); // SEEK @@ -566,7 +566,7 @@ internal static extern bool EvtSeek( long position, EventLogHandle bookmark, int timeout, - [MarshalAs(UnmanagedType.I4)]EvtSeekFlags flags + [MarshalAs(UnmanagedType.I4)] EvtSeekFlags flags ); [DllImport(WEVTAPI, CallingConvention = CallingConvention.Winapi, SetLastError = true)] @@ -603,7 +603,7 @@ IntPtr eventHandle internal static extern bool EvtGetEventInfo( EventLogHandle eventHandle, // int propertyId - [MarshalAs(UnmanagedType.I4)]EvtEventPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtEventPropertyId propertyId, int bufferSize, IntPtr bufferPtr, out int bufferUsed @@ -614,7 +614,7 @@ out int bufferUsed [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtGetQueryInfo( EventLogHandle queryHandle, - [MarshalAs(UnmanagedType.I4)]EvtQueryPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtQueryPropertyId propertyId, int bufferSize, IntPtr buffer, ref int bufferRequired @@ -686,7 +686,7 @@ int flags [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtGetEventMetadataProperty( EventLogHandle eventMetadata, - [MarshalAs(UnmanagedType.I4)] EvtEventMetadataPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtEventMetadataPropertyId propertyId, int flags, int eventMetadataPropertyBufferSize, IntPtr eventMetadataPropertyBuffer, @@ -708,7 +708,7 @@ internal static extern bool EvtNextChannelPath( EventLogHandle channelEnum, int channelPathBufferSize, // StringBuilder channelPathBuffer, - [Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder channelPathBuffer, + [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder channelPathBuffer, out int channelPathBufferUsed ); @@ -725,7 +725,7 @@ int flags internal static extern bool EvtNextPublisherId( EventLogHandle publisherEnum, int publisherIdBufferSize, - [Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder publisherIdBuffer, + [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder publisherIdBuffer, out int publisherIdBufferUsed ); @@ -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 ); @@ -750,7 +750,7 @@ int flags [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtSetChannelConfigProperty( EventLogHandle channelConfig, - [MarshalAs(UnmanagedType.I4)]EvtChannelConfigPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtChannelConfigPropertyId propertyId, int flags, ref EvtVariant propertyValue ); @@ -760,7 +760,7 @@ ref EvtVariant propertyValue [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtGetChannelConfigProperty( EventLogHandle channelConfig, - [MarshalAs(UnmanagedType.I4)]EvtChannelConfigPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtChannelConfigPropertyId propertyId, int flags, int propertyValueBufferSize, IntPtr propertyValueBuffer, @@ -773,7 +773,7 @@ out int propertyValueBufferUsed internal static extern EventLogHandle EvtOpenLog( EventLogHandle session, [MarshalAs(UnmanagedType.LPWStr)] string path, - [MarshalAs(UnmanagedType.I4)]PathType flags + [MarshalAs(UnmanagedType.I4)] PathType flags ); [DllImport(WEVTAPI, CharSet = CharSet.Unicode, SetLastError = true)] @@ -781,7 +781,7 @@ internal static extern EventLogHandle EvtOpenLog( [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtGetLogInfo( EventLogHandle log, - [MarshalAs(UnmanagedType.I4)]EvtLogPropertyId propertyId, + [MarshalAs(UnmanagedType.I4)] EvtLogPropertyId propertyId, int propertyValueBufferSize, IntPtr propertyValueBuffer, out int propertyValueBufferUsed @@ -793,9 +793,9 @@ out int propertyValueBufferUsed [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtExportLog( EventLogHandle session, - [MarshalAs(UnmanagedType.LPWStr)]string channelPath, - [MarshalAs(UnmanagedType.LPWStr)]string query, - [MarshalAs(UnmanagedType.LPWStr)]string targetFilePath, + [MarshalAs(UnmanagedType.LPWStr)] string channelPath, + [MarshalAs(UnmanagedType.LPWStr)] string query, + [MarshalAs(UnmanagedType.LPWStr)] string targetFilePath, int flags ); @@ -804,7 +804,7 @@ int flags [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtArchiveExportedLog( EventLogHandle session, - [MarshalAs(UnmanagedType.LPWStr)]string logFilePath, + [MarshalAs(UnmanagedType.LPWStr)] string logFilePath, int locale, int flags ); @@ -814,8 +814,8 @@ int flags [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool EvtClearLog( EventLogHandle session, - [MarshalAs(UnmanagedType.LPWStr)]string channelPath, - [MarshalAs(UnmanagedType.LPWStr)]string targetFilePath, + [MarshalAs(UnmanagedType.LPWStr)] string channelPath, + [MarshalAs(UnmanagedType.LPWStr)] string targetFilePath, int flags ); @@ -823,10 +823,10 @@ 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 + [MarshalAs(UnmanagedType.I4)] EvtRenderContextFlags flags ); [DllImport(WEVTAPI, CallingConvention = CallingConvention.Winapi, SetLastError = true)] @@ -837,7 +837,7 @@ internal static extern bool EvtRender( EventLogHandle eventHandle, EvtRenderFlags flags, int buffSize, - [Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder buffer, + [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder buffer, out int buffUsed, out int propCount ); @@ -862,11 +862,11 @@ 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)] [SecurityCritical] @@ -877,9 +877,9 @@ internal static extern bool EvtFormatMessage( uint messageId, int valueCount, EvtStringVariant[] values, - [MarshalAs(UnmanagedType.I4)]EvtFormatMessageFlags flags, + [MarshalAs(UnmanagedType.I4)] EvtFormatMessageFlags flags, int bufferSize, - [Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder buffer, + [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder buffer, out int bufferUsed ); @@ -892,7 +892,7 @@ internal static extern bool EvtFormatMessageBuffer( uint messageId, int valueCount, IntPtr values, - [MarshalAs(UnmanagedType.I4)]EvtFormatMessageFlags flags, + [MarshalAs(UnmanagedType.I4)] EvtFormatMessageFlags flags, int bufferSize, IntPtr buffer, out int bufferUsed @@ -902,7 +902,7 @@ out int bufferUsed [DllImport(WEVTAPI, CharSet = CharSet.Unicode, SetLastError = true)] [SecurityCritical] internal static extern EventLogHandle EvtOpenSession( - [MarshalAs(UnmanagedType.I4)]EvtLoginClass loginClass, + [MarshalAs(UnmanagedType.I4)] EvtLoginClass loginClass, ref EvtRpcLogin login, int timeout, int flags 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 22054aa3eda..dadff652e53 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -2,13 +2,13 @@ PowerShell's Microsoft.PowerShell.CoreCLR.Eventing project - $(NoWarn);CS1591 + $(NoWarn);CS1591;CA1416 Microsoft.PowerShell.CoreCLR.Eventing - + 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/AddLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs index 375a8101075..59cb3067efc 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs @@ -301,4 +301,3 @@ private void ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs index 09e7bdf9452..592c77726fe 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs @@ -217,4 +217,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs index e321a03e266..88627ba2e33 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs @@ -217,4 +217,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs index 3965c362335..4c11a3c4c36 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs @@ -168,4 +168,3 @@ private void ProcessSids() } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs index a10300e9065..0b3625f6ef7 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; } @@ -233,4 +233,3 @@ private IEnumerable ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs index e469d043ffa..9f8d3b9311b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs @@ -170,4 +170,3 @@ private void ProcessSids() } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs index 59e4f4ca13f..b709d22a485 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs @@ -118,4 +118,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs index b3916f46071..d3402b5a4c9 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs @@ -292,4 +292,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs index 0c0af710af1..981643cf04c 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs @@ -210,4 +210,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs index 7e132405b2a..47d0a77784e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs @@ -299,4 +299,3 @@ private void ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs index 0c61da2e117..6d6081fef7e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs @@ -211,4 +211,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs index f32e3365086..c594fccb889 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs @@ -230,4 +230,3 @@ private bool CheckShouldProcess(string groupName, string newName) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs index e6b1297a7d5..c23cf41ac61 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs @@ -230,4 +230,3 @@ private bool CheckShouldProcess(string userName, string newName) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs index b1943971e3d..49ae31dacc7 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs @@ -177,4 +177,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs index 8fafa52c9e4..3bfdc24ac03 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs @@ -320,4 +320,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.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.MarkdownRender/CodeInlineRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/CodeInlineRenderer.cs deleted file mode 100644 index 681c38cc6d8..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/CodeInlineRenderer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for inline code elements. - /// - internal class CodeInlineRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, CodeInline obj) - { - renderer.Write(renderer.EscapeSequences.FormatCode(obj.Content, isInline: true)); - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/EmphasisInlineRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/EmphasisInlineRenderer.cs deleted file mode 100644 index 8a4be614796..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/EmphasisInlineRenderer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for bold and italics elements. - /// - internal class EmphasisInlineRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, EmphasisInline obj) - { - renderer.Write(renderer.EscapeSequences.FormatEmphasis(obj.FirstChild.ToString(), isBold: obj.DelimiterCount == 2 ? true : false)); - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/FencedCodeBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/FencedCodeBlockRenderer.cs deleted file mode 100644 index 121c7ee01fa..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/FencedCodeBlockRenderer.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Helpers; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for code blocks with language type. - /// - internal class FencedCodeBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, FencedCodeBlock obj) - { - if (obj?.Lines.Lines != null) - { - foreach (StringLine codeLine in obj.Lines.Lines) - { - if (!string.IsNullOrWhiteSpace(codeLine.ToString())) - { - // If the code block is of type YAML, then tab to right to improve readability. - // This specifically helps for parameters help content. - if (string.Equals(obj.Info, "yaml", StringComparison.OrdinalIgnoreCase)) - { - renderer.Write("\t").WriteLine(codeLine.ToString()); - } - else - { - renderer.WriteLine(renderer.EscapeSequences.FormatCode(codeLine.ToString(), isInline: false)); - } - } - } - - // Add a blank line after the code block for better readability. - renderer.WriteLine(); - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/HeaderBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/HeaderBlockRenderer.cs deleted file mode 100644 index eb975d6b6a3..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/HeaderBlockRenderer.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for headings. - /// - internal class HeaderBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, HeadingBlock obj) - { - string headerText = obj?.Inline?.FirstChild?.ToString(); - - if (!string.IsNullOrEmpty(headerText)) - { - // Format header and then add blank line to improve readability. - switch (obj.Level) - { - case 1: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader1(headerText)); - renderer.WriteLine(); - break; - - case 2: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader2(headerText)); - renderer.WriteLine(); - break; - - case 3: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader3(headerText)); - renderer.WriteLine(); - break; - - case 4: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader4(headerText)); - renderer.WriteLine(); - break; - - case 5: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader5(headerText)); - renderer.WriteLine(); - break; - - case 6: - renderer.WriteLine(renderer.EscapeSequences.FormatHeader6(headerText)); - renderer.WriteLine(); - break; - } - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/LeafInlineRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/LeafInlineRenderer.cs deleted file mode 100644 index 55a8ff32a73..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/LeafInlineRenderer.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for leaf elements like plain text in paragraphs. - /// - internal class LeafInlineRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, LeafInline obj) - { - // If the next sibling is null, then this is the last line in the paragraph. - // Add new line character at the end. - // Else just write without newline at the end. - if (obj.NextSibling == null) - { - renderer.WriteLine(obj.ToString()); - } - else - { - renderer.Write(obj.ToString()); - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/LineBreakRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/LineBreakRenderer.cs deleted file mode 100644 index 1f4c8f4ceaf..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/LineBreakRenderer.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for line breaks. - /// - internal class LineBreakRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, LineBreakInline obj) - { - // If it is a hard line break add new line at the end. - // Else, add a space for after the last character to improve readability. - if (obj.IsHard) - { - renderer.WriteLine(); - } - else - { - renderer.Write(" "); - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/LinkInlineRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/LinkInlineRenderer.cs deleted file mode 100644 index 3999fd5f4e1..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/LinkInlineRenderer.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax.Inlines; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for links. - /// - internal class LinkInlineRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, LinkInline obj) - { - string text = obj.FirstChild?.ToString(); - - // Format link as image or link. - if (obj.IsImage) - { - renderer.Write(renderer.EscapeSequences.FormatImage(text)); - } - else - { - renderer.Write(renderer.EscapeSequences.FormatLink(text, obj.Url)); - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs deleted file mode 100644 index b65d7c25475..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for list blocks. - /// - internal class ListBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ListBlock obj) - { - // start index of a numbered block. - int index = 1; - - foreach (var item in obj) - { - if (item is ListItemBlock listItem) - { - if (obj.IsOrdered) - { - RenderNumberedList(renderer, listItem, index++); - } - else - { - renderer.Write(listItem); - } - } - } - - renderer.WriteLine(); - } - - private void RenderNumberedList(VT100Renderer renderer, ListItemBlock block, int index) - { - // For a numbered list, we need to make sure the index is incremented. - foreach (var line in block) - { - if (line is ParagraphBlock paragraphBlock) - { - renderer.Write(index.ToString()).Write(". ").Write(paragraphBlock.Inline); - } - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs deleted file mode 100644 index 281cec47d54..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; -using System.Threading; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for items in a list block. - /// - internal class ListItemBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ListItemBlock obj) - { - if (obj.Parent is ListBlock parent) - { - if (!parent.IsOrdered) - { - foreach (var line in obj) - { - RenderWithIndent(renderer, line, parent.BulletType, 0); - } - } - } - } - - private void RenderWithIndent(VT100Renderer renderer, MarkdownObject block, char listBullet, int indentLevel) - { - // Indent left by 2 for each level on list. - string indent = Padding(indentLevel * 2); - - if (block is ParagraphBlock paragraphBlock) - { - renderer.Write(indent).Write(listBullet).Write(" ").Write(paragraphBlock.Inline); - } - else - { - // If there is a sublist, the block is a ListBlock instead of ParagraphBlock. - if (block is ListBlock subList) - { - foreach (var subListItem in subList) - { - if (subListItem is ListItemBlock subListItemBlock) - { - foreach (var line in subListItemBlock) - { - // Increment indent level for sub list. - RenderWithIndent(renderer, line, listBullet, indentLevel + 1); - } - } - } - } - } - } - - // Typical padding is at most a screen's width, any more than that and we won't bother caching. - private const int IndentCacheMax = 120; - - private static readonly string[] IndentCache = new string[IndentCacheMax]; - - internal static string Padding(int countOfSpaces) - { - if (countOfSpaces >= IndentCacheMax) - { - return new string(' ', countOfSpaces); - } - - var result = IndentCache[countOfSpaces]; - - if (result == null) - { - Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), comparand: null); - result = IndentCache[countOfSpaces]; - } - - return result; - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/MarkdownConverter.cs b/src/Microsoft.PowerShell.MarkdownRender/MarkdownConverter.cs deleted file mode 100644 index c68d9af3d3b..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/MarkdownConverter.cs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Type of conversion from Markdown. - /// - [Flags] - public enum MarkdownConversionType - { - /// - /// Convert to HTML. - /// - HTML = 1, - - /// - /// Convert to VT100 encoded string. - /// - VT100 = 2 - } - - /// - /// Object representing the conversion from Markdown. - /// - public class MarkdownInfo - { - /// - /// Gets the Html content after conversion. - /// - public string Html { get; internal set; } - - /// - /// Gets the VT100 encoded string after conversion. - /// - public string VT100EncodedString { get; internal set; } - - /// - /// Gets the AST of the Markdown string. - /// - public Markdig.Syntax.MarkdownDocument Tokens { get; internal set; } - } - - /// - /// Class to convert a Markdown string to VT100, HTML or AST. - /// - public sealed class MarkdownConverter - { - /// - /// Convert from Markdown string to VT100 encoded string or HTML. Returns MarkdownInfo object. - /// - /// String with Markdown content to be converted. - /// Specifies type of conversion, either VT100 or HTML. - /// Specifies the rendering options for VT100 rendering. - /// MarkdownInfo object with the converted output. - public static MarkdownInfo Convert(string markdownString, MarkdownConversionType conversionType, PSMarkdownOptionInfo optionInfo) - { - var renderInfo = new MarkdownInfo(); - var writer = new StringWriter(); - MarkdownPipeline pipeline = null; - - if (conversionType.HasFlag(MarkdownConversionType.HTML)) - { - pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); - var renderer = new Markdig.Renderers.HtmlRenderer(writer); - renderInfo.Html = Markdig.Markdown.Convert(markdownString, renderer, pipeline).ToString(); - } - - if (conversionType.HasFlag(MarkdownConversionType.VT100)) - { - pipeline = new MarkdownPipelineBuilder().Build(); - - // Use the VT100 renderer. - var renderer = new VT100Renderer(writer, optionInfo); - renderInfo.VT100EncodedString = Markdig.Markdown.Convert(markdownString, renderer, pipeline).ToString(); - } - - // Always have AST available. - var parsed = Markdig.Markdown.Parse(markdownString, pipeline); - renderInfo.Tokens = parsed; - - return renderInfo; - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj b/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj deleted file mode 100644 index 6cc2b02c660..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - PowerShell's Markdown Rendering project - Microsoft.PowerShell.MarkdownRender - - - - - - - - - diff --git a/src/Microsoft.PowerShell.MarkdownRender/ParagraphBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/ParagraphBlockRenderer.cs deleted file mode 100644 index 26986311614..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/ParagraphBlockRenderer.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for paragraphs. - /// - internal class ParagraphBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, ParagraphBlock obj) - { - // Call the renderer for children, leaf inline or line breaks. - renderer.WriteChildren(obj.Inline); - - // Add new line at the end of the paragraph. - renderer.WriteLine(); - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/QuoteBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/QuoteBlockRenderer.cs deleted file mode 100644 index 1f26adc5883..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/QuoteBlockRenderer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Renderer for adding VT100 escape sequences for quote blocks. - /// - internal class QuoteBlockRenderer : VT100ObjectRenderer - { - protected override void Write(VT100Renderer renderer, QuoteBlock obj) - { - // Iterate through each item and add the quote character before the content. - foreach (var item in obj) - { - renderer.Write(obj.QuoteChar).Write(" ").Write(item); - } - - // Add blank line after the quote block. - renderer.WriteLine(); - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/VT100EscapeSequences.cs b/src/Microsoft.PowerShell.MarkdownRender/VT100EscapeSequences.cs deleted file mode 100644 index 0827fdf049f..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/VT100EscapeSequences.cs +++ /dev/null @@ -1,483 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; -using System.Management.Automation; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Enum to name all the properties of PSMarkdownOptionInfo. - /// - public enum MarkdownOptionInfoProperty - { - /// - /// Property name Header1. - /// - Header1, - - /// - /// Property name Header2. - /// - Header2, - - /// - /// Property name Header3. - /// - Header3, - - /// - /// Property name Header4. - /// - Header4, - - /// - /// Property name Header5. - /// - Header5, - - /// - /// Property name Header6. - /// - Header6, - - /// - /// Property name Code. - /// - Code, - - /// - /// Property name Link. - /// - Link, - - /// - /// Property name Image. - /// - Image, - - /// - /// Property name EmphasisBold. - /// - EmphasisBold, - - /// - /// Property name EmphasisItalics. - /// - EmphasisItalics - } - - /// - /// Class to represent color preference options for various Markdown elements. - /// - public sealed class PSMarkdownOptionInfo - { - private const char Esc = (char)0x1b; - private const string EndSequence = "[0m"; - - /// - /// Gets or sets current VT100 escape sequence for header 1. - /// - public string Header1 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 2. - /// - public string Header2 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 3. - /// - public string Header3 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 4. - /// - public string Header4 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 5. - /// - public string Header5 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for header 6. - /// - public string Header6 { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for code inline and code blocks. - /// - public string Code { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for links. - /// - public string Link { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for images. - /// - public string Image { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for bold text. - /// - public string EmphasisBold { get; set; } - - /// - /// Gets or sets current VT100 escape sequence for italics text. - /// - public string EmphasisItalics { get; set; } - - /// - /// Gets or sets a value indicating whether VT100 escape sequences should be added. Default it true. - /// - public bool EnableVT100Encoding { get; set; } - - /// - /// Get the property as an rendered escape sequence. - /// This is used by formatting system for displaying. - /// - /// Name of the property to get as escape sequence. - /// Specified property name as escape sequence. - public string AsEscapeSequence(MarkdownOptionInfoProperty propertyName) - { - switch (propertyName) - { - case MarkdownOptionInfoProperty.Header1: - return string.Concat(Esc, Header1, Header1, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Header2: - return string.Concat(Esc, Header2, Header2, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Header3: - return string.Concat(Esc, Header3, Header3, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Header4: - return string.Concat(Esc, Header4, Header4, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Header5: - return string.Concat(Esc, Header5, Header5, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Header6: - return string.Concat(Esc, Header6, Header6, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Code: - return string.Concat(Esc, Code, Code, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Link: - return string.Concat(Esc, Link, Link, Esc, EndSequence); - - case MarkdownOptionInfoProperty.Image: - return string.Concat(Esc, Image, Image, Esc, EndSequence); - - case MarkdownOptionInfoProperty.EmphasisBold: - return string.Concat(Esc, EmphasisBold, EmphasisBold, Esc, EndSequence); - - case MarkdownOptionInfoProperty.EmphasisItalics: - return string.Concat(Esc, EmphasisItalics, EmphasisItalics, Esc, EndSequence); - - default: - break; - } - - return null; - } - - /// - /// Initializes a new instance of the class and sets dark as the default theme. - /// - public PSMarkdownOptionInfo() - { - SetDarkTheme(); - EnableVT100Encoding = true; - } - - private const string Header1Dark = "[7m"; - private const string Header2Dark = "[4;93m"; - private const string Header3Dark = "[4;94m"; - private const string Header4Dark = "[4;95m"; - private const string Header5Dark = "[4;96m"; - private const string Header6Dark = "[4;97m"; - private const string CodeDark = "[48;2;155;155;155;38;2;30;30;30m"; - private const string CodeMacOS = "[107;95m"; - private const string LinkDark = "[4;38;5;117m"; - private const string ImageDark = "[33m"; - private const string EmphasisBoldDark = "[1m"; - private const string EmphasisItalicsDark = "[36m"; - - private const string Header1Light = "[7m"; - private const string Header2Light = "[4;33m"; - private const string Header3Light = "[4;34m"; - private const string Header4Light = "[4;35m"; - private const string Header5Light = "[4;36m"; - private const string Header6Light = "[4;30m"; - private const string CodeLight = "[48;2;155;155;155;38;2;30;30;30m"; - private const string LinkLight = "[4;38;5;117m"; - private const string ImageLight = "[33m"; - private const string EmphasisBoldLight = "[1m"; - private const string EmphasisItalicsLight = "[36m"; - - /// - /// Set all preference for dark theme. - /// - public void SetDarkTheme() - { - Header1 = Header1Dark; - Header2 = Header2Dark; - Header3 = Header3Dark; - Header4 = Header4Dark; - Header5 = Header5Dark; - Header6 = Header6Dark; - Link = LinkDark; - Image = ImageDark; - EmphasisBold = EmphasisBoldDark; - EmphasisItalics = EmphasisItalicsDark; - SetCodeColor(isDarkTheme: true); - } - - /// - /// Set all preference for light theme. - /// - public void SetLightTheme() - { - Header1 = Header1Light; - Header2 = Header2Light; - Header3 = Header3Light; - Header4 = Header4Light; - Header5 = Header5Light; - Header6 = Header6Light; - Link = LinkLight; - Image = ImageLight; - EmphasisBold = EmphasisBoldLight; - EmphasisItalics = EmphasisItalicsLight; - SetCodeColor(isDarkTheme: false); - } - - private void SetCodeColor(bool isDarkTheme) - { - // MacOS terminal app does not support extended colors for VT100, so we special case for it. - Code = Platform.IsMacOS ? CodeMacOS : isDarkTheme ? CodeDark : CodeLight; - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - public class VT100EscapeSequences - { - private const char Esc = (char)0x1B; - - private string endSequence = Esc + "[0m"; - - // For code blocks, [500@ make sure that the whole line has background color. - private const string LongBackgroundCodeBlock = "[500@"; - - private PSMarkdownOptionInfo options; - - /// - /// Initializes a new instance of the class. - /// - /// PSMarkdownOptionInfo object to initialize with. - public VT100EscapeSequences(PSMarkdownOptionInfo optionInfo) - { - if (optionInfo == null) - { - throw new ArgumentNullException(nameof(optionInfo)); - } - - options = optionInfo; - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 1 string. - public string FormatHeader1(string headerText) - { - return FormatHeader(headerText, options.Header1); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 2 string. - public string FormatHeader2(string headerText) - { - return FormatHeader(headerText, options.Header2); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 3 string. - public string FormatHeader3(string headerText) - { - return FormatHeader(headerText, options.Header3); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 4 string. - public string FormatHeader4(string headerText) - { - return FormatHeader(headerText, options.Header4); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 5 string. - public string FormatHeader5(string headerText) - { - return FormatHeader(headerText, options.Header5); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the header to format. - /// Formatted Header 6 string. - public string FormatHeader6(string headerText) - { - return FormatHeader(headerText, options.Header6); - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the code block to format. - /// True if it is a inline code block, false otherwise. - /// Formatted code block string. - public string FormatCode(string codeText, bool isInline) - { - bool isVT100Enabled = options.EnableVT100Encoding; - - if (isInline) - { - if (isVT100Enabled) - { - return string.Concat(Esc, options.Code, codeText, endSequence); - } - else - { - return codeText; - } - } - else - { - if (isVT100Enabled) - { - return string.Concat(Esc, options.Code, codeText, Esc, LongBackgroundCodeBlock, endSequence); - } - else - { - return codeText; - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the link to format. - /// URL of the link. - /// True url should be hidden, false otherwise. Default is true. - /// Formatted link string. - public string FormatLink(string linkText, string url, bool hideUrl = true) - { - bool isVT100Enabled = options.EnableVT100Encoding; - - if (hideUrl) - { - if (isVT100Enabled) - { - return string.Concat(Esc, options.Link, "\"", linkText, "\"", endSequence); - } - else - { - return string.Concat("\"", linkText, "\""); - } - } - else - { - if (isVT100Enabled) - { - return string.Concat("\"", linkText, "\" (", Esc, options.Link, url, endSequence, ")"); - } - else - { - return string.Concat("\"", linkText, "\" (", url, ")"); - } - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text to format as emphasis. - /// True if it is to be formatted as bold, false to format it as italics. - /// Formatted emphasis string. - public string FormatEmphasis(string emphasisText, bool isBold) - { - var sequence = isBold ? options.EmphasisBold : options.EmphasisItalics; - - if (options.EnableVT100Encoding) - { - return string.Concat(Esc, sequence, emphasisText, endSequence); - } - else - { - return emphasisText; - } - } - - /// - /// Class to represent default VT100 escape sequences. - /// - /// Text of the image to format. - /// Formatted image string. - public string FormatImage(string altText) - { - var text = altText; - - if (string.IsNullOrEmpty(altText)) - { - text = "Image"; - } - - if (options.EnableVT100Encoding) - { - return string.Concat(Esc, options.Image, "[", text, "]", endSequence); - } - else - { - return string.Concat("[", text, "]"); - } - } - - private string FormatHeader(string headerText, string headerEscapeSequence) - { - if (options.EnableVT100Encoding) - { - return string.Concat(Esc, headerEscapeSequence, headerText, endSequence); - } - else - { - return headerText; - } - } - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/VT100ObjectRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/VT100ObjectRenderer.cs deleted file mode 100644 index 566a086015d..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/VT100ObjectRenderer.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Implement the MarkdownObjectRenderer with VT100Renderer. - /// - /// The element type of the renderer. - public abstract class VT100ObjectRenderer : MarkdownObjectRenderer where T : MarkdownObject - { - } -} diff --git a/src/Microsoft.PowerShell.MarkdownRender/VT100Renderer.cs b/src/Microsoft.PowerShell.MarkdownRender/VT100Renderer.cs deleted file mode 100644 index c817cea7e87..00000000000 --- a/src/Microsoft.PowerShell.MarkdownRender/VT100Renderer.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System; -using System.IO; - -using Markdig; -using Markdig.Renderers; -using Markdig.Syntax; - -namespace Microsoft.PowerShell.MarkdownRender -{ - /// - /// Initializes an instance of the VT100 renderer. - /// - public sealed class VT100Renderer : TextRendererBase - { - /// - /// Initializes a new instance of the class. - /// - /// TextWriter to write to. - /// PSMarkdownOptionInfo object with options. - public VT100Renderer(TextWriter writer, PSMarkdownOptionInfo optionInfo) : base(writer) - { - EscapeSequences = new VT100EscapeSequences(optionInfo); - - // Add the various element renderers. - ObjectRenderers.Add(new HeaderBlockRenderer()); - ObjectRenderers.Add(new LineBreakRenderer()); - ObjectRenderers.Add(new CodeInlineRenderer()); - ObjectRenderers.Add(new FencedCodeBlockRenderer()); - ObjectRenderers.Add(new EmphasisInlineRenderer()); - ObjectRenderers.Add(new ParagraphBlockRenderer()); - ObjectRenderers.Add(new LeafInlineRenderer()); - ObjectRenderers.Add(new LinkInlineRenderer()); - ObjectRenderers.Add(new ListBlockRenderer()); - ObjectRenderers.Add(new ListItemBlockRenderer()); - ObjectRenderers.Add(new QuoteBlockRenderer()); - } - - /// - /// Gets the current escape sequences. - /// - public VT100EscapeSequences EscapeSequences { get; private set; } - } -} diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index 2f3a1fc3272..50d431225c7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -15,22 +15,41 @@ + + + - + - - - + + - - - - - - + + + - - + - + + + + + <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' " /> + + + + + \ No newline at end of file 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 48c9fd056d7..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJob.cs +++ /dev/null @@ -1,1291 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - private ScheduledJob( - SerializationInfo info, - StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException("info"); - } - - DeserializeStatusInfo(info); - DeserializeResultsInfo(info); - PSJobTypeName = ScheduledJobSourceAdapter.AdapterTypeName; - } - - /// - /// Serialize method. - /// - /// SerializationInfo. - /// StreamingContext. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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(); - } - } - - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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; - } - } - - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 650c643ecf9..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobDefinition.cs +++ /dev/null @@ -1,2587 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 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 2daedddc554..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobOptions.cs +++ /dev/null @@ -1,407 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 a507e3161af..00000000000 --- a/src/Microsoft.PowerShell.ScheduledJob/ScheduledJobTrigger.cs +++ /dev/null @@ -1,899 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. - /// - /// - /// - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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/Microsoft.PowerShell.Security.csproj b/src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj index 41b798f6e6d..a6dadabfed2 100644 --- a/src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj +++ b/src/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.csproj @@ -2,7 +2,7 @@ PowerShell's Microsoft.PowerShell.Security project - $(NoWarn);CS1570 + $(NoWarn);CS1570;CA1416 Microsoft.PowerShell.Security 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 03fa060154b..e39b296d154 100644 --- a/src/Microsoft.PowerShell.Security/security/AclCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/AclCommands.cs @@ -5,18 +5,18 @@ #pragma warning disable 56506 using System; -using System.Management.Automation; -using Dbg = System.Management.Automation; -using System.Management.Automation.Security; -using System.Security.AccessControl; -using System.Security.Principal; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; using System.Globalization; -using System.ComponentModel; -using System.Reflection; +using System.Management.Automation; +using System.Management.Automation.Security; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Security.Principal; + +using Dbg = System.Management.Automation; namespace Microsoft.PowerShell.Commands { @@ -87,8 +87,7 @@ internal CmdletProviderContext CmdletProviderContext { get { - CmdletProviderContext coreCommandContext = - new CmdletProviderContext(this); + CmdletProviderContext coreCommandContext = new(this); Collection includeFilter = SessionStateUtilities.ConvertArrayToCollection(Include); @@ -208,8 +207,7 @@ public static string GetOwner(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - ObjectSecurity sd = instance.BaseObject as ObjectSecurity; - if (sd == null) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -248,8 +246,7 @@ public static string GetGroup(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - ObjectSecurity sd = instance.BaseObject as ObjectSecurity; - if (sd == null) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -294,20 +291,16 @@ public static AuthorizationRuleCollection GetAccess(PSObject instance) } // Get DACL - AuthorizationRuleCollection dacl; - CommonObjectSecurity cos = sd as CommonObjectSecurity; - if (cos != null) + if (sd is CommonObjectSecurity cos) { - dacl = cos.GetAccessRules(true, true, typeof(NTAccount)); + return cos.GetAccessRules(true, true, typeof(NTAccount)); } else { DirectoryObjectSecurity dos = sd as DirectoryObjectSecurity; Dbg.Diagnostics.Assert(dos != null, "Acl should be of type CommonObjectSecurity or DirectoryObjectSecurity"); - dacl = dos.GetAccessRules(true, true, typeof(NTAccount)); + return dos.GetAccessRules(true, true, typeof(NTAccount)); } - - return dacl; } /// @@ -332,20 +325,16 @@ public static AuthorizationRuleCollection GetAudit(PSObject instance) PSTraceSource.NewArgumentException(nameof(instance)); } - AuthorizationRuleCollection sacl; - CommonObjectSecurity cos = sd as CommonObjectSecurity; - if (cos != null) + if (sd is CommonObjectSecurity cos) { - sacl = cos.GetAuditRules(true, true, typeof(NTAccount)); + return cos.GetAuditRules(true, true, typeof(NTAccount)); } else { DirectoryObjectSecurity dos = sd as DirectoryObjectSecurity; Dbg.Diagnostics.Assert(dos != null, "Acl should be of type CommonObjectSecurity or DirectoryObjectSecurity"); - sacl = dos.GetAuditRules(true, true, typeof(NTAccount)); + return dos.GetAuditRules(true, true, typeof(NTAccount)); } - - return sacl; } /// @@ -359,11 +348,10 @@ public static AuthorizationRuleCollection GetAudit(PSObject instance) /// public static SecurityIdentifier GetCentralAccessPolicyId(PSObject instance) { - SessionState sessionState = new SessionState(); + SessionState sessionState = new(); string path = sessionState.Path.GetUnresolvedProviderPathFromPSPath( GetPath(instance)); - IntPtr pOwner = IntPtr.Zero, pGroup = IntPtr.Zero; - IntPtr pDacl = IntPtr.Zero, pSacl = IntPtr.Zero, pSd = IntPtr.Zero; + IntPtr pSd = IntPtr.Zero; try { @@ -372,10 +360,10 @@ public static SecurityIdentifier GetCentralAccessPolicyId(PSObject instance) path, NativeMethods.SeObjectType.SE_FILE_OBJECT, NativeMethods.SecurityInformation.SCOPE_SECURITY_INFORMATION, - out pOwner, - out pGroup, - out pDacl, - out pSacl, + out IntPtr pOwner, + out IntPtr pGroup, + out IntPtr pDacl, + out IntPtr pSacl, out pSd); if (rs != NativeMethods.ERROR_SUCCESS) { @@ -387,8 +375,7 @@ public static SecurityIdentifier GetCentralAccessPolicyId(PSObject instance) return null; } - NativeMethods.ACL sacl = new NativeMethods.ACL(); - sacl = Marshal.PtrToStructure(pSacl); + NativeMethods.ACL sacl = Marshal.PtrToStructure(pSacl); if (sacl.AceCount == 0) { return null; @@ -398,8 +385,7 @@ public static SecurityIdentifier GetCentralAccessPolicyId(PSObject instance) IntPtr pAce = pSacl + Marshal.SizeOf(new NativeMethods.ACL()); for (ushort aceIdx = 0; aceIdx < sacl.AceCount; aceIdx++) { - NativeMethods.ACE_HEADER ace = new NativeMethods.ACE_HEADER(); - ace = Marshal.PtrToStructure(pAce); + NativeMethods.ACE_HEADER ace = Marshal.PtrToStructure(pAce); Dbg.Diagnostics.Assert(ace.AceType == NativeMethods.SYSTEM_SCOPED_POLICY_ID_ACE_TYPE, "Unexpected ACE type: " + ace.AceType.ToString(CultureInfo.CurrentCulture)); @@ -460,12 +446,11 @@ public static string GetCentralAccessPolicyName(PSObject instance) Marshal.Copy(capIdArray, 0, pCapId, capIdSize); IntPtr[] ppCapId = new IntPtr[1]; ppCapId[0] = pCapId; - uint capCount = 0; uint rs = NativeMethods.LsaQueryCAPs( ppCapId, 1, out caps, - out capCount); + out uint capCount); if (rs != NativeMethods.STATUS_SUCCESS) { throw new Win32Exception((int)rs); @@ -477,8 +462,7 @@ public static string GetCentralAccessPolicyName(PSObject instance) } // Get the CAP name. - NativeMethods.CENTRAL_ACCESS_POLICY cap = new NativeMethods.CENTRAL_ACCESS_POLICY(); - cap = Marshal.PtrToStructure(caps); + NativeMethods.CENTRAL_ACCESS_POLICY cap = Marshal.PtrToStructure(caps); // LSA_UNICODE_STRING is composed of WCHARs, but its length is given in bytes. return Marshal.PtrToStringUni(cap.Name.Buffer, cap.Name.Length / 2); } @@ -510,12 +494,11 @@ public static string[] GetAllCentralAccessPolicies(PSObject instance) try { // Retrieve all CAPs. - uint capCount = 0; uint rs = NativeMethods.LsaQueryCAPs( null, 0, out caps, - out capCount); + out uint capCount); if (rs != NativeMethods.STATUS_SUCCESS) { throw new Win32Exception((int)rs); @@ -530,14 +513,13 @@ public static string[] GetAllCentralAccessPolicies(PSObject instance) // Add CAP names and IDs to a string array. string[] policies = new string[capCount]; - NativeMethods.CENTRAL_ACCESS_POLICY cap = new NativeMethods.CENTRAL_ACCESS_POLICY(); IntPtr capPtr = caps; for (uint capIdx = 0; capIdx < capCount; capIdx++) { // Retrieve CAP name. Dbg.Diagnostics.Assert(capPtr != IntPtr.Zero, "Invalid central access policies array"); - cap = Marshal.PtrToStructure(capPtr); + NativeMethods.CENTRAL_ACCESS_POLICY cap = Marshal.PtrToStructure(capPtr); // LSA_UNICODE_STRING is composed of WCHARs, but its length is given in bytes. policies[capIdx] = "\"" + Marshal.PtrToStringUni( cap.Name.Buffer, @@ -588,8 +570,7 @@ public static string GetSddl(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - ObjectSecurity sd = instance.BaseObject as ObjectSecurity; - if (sd == null) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -642,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 @@ -656,7 +637,7 @@ public string[] Path } } - private PSObject _inputObject = null; + private PSObject _inputObject; /// /// InputObject Parameter @@ -681,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 { @@ -698,13 +679,13 @@ public string[] LiteralPath } } - private bool _isLiteralPath = false; + private bool _isLiteralPath; /// /// 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 @@ -735,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 @@ -761,7 +742,6 @@ public SwitchParameter AllCentralAccessPolicies /// protected override void ProcessRecord() { - Collection sd = null; AccessControlSections sections = AccessControlSections.Owner | AccessControlSections.Group | @@ -783,7 +763,7 @@ protected override void ProcessRecord() { customDescriptor = PSObject.Base(methodInfo.Invoke()); - if (!(customDescriptor is FileSystemSecurity)) + if (customDescriptor is not FileSystemSecurity) { customDescriptor = new CommonSecurityDescriptor(false, false, customDescriptor.ToString()); } @@ -818,14 +798,14 @@ protected override void ProcessRecord() { foreach (string p in Path) { - List pathsToProcess = new List(); + List pathsToProcess = new(); string currentPath = null; try { if (_isLiteralPath) { - pathsToProcess.Add(SessionState.Path.GetUnresolvedProviderPathFromPSPath(p)); + pathsToProcess.Add(p); } else { @@ -841,7 +821,7 @@ protected override void ProcessRecord() { currentPath = rp; - CmdletProviderContext context = new CmdletProviderContext(this.Context); + CmdletProviderContext context = new(this.Context); context.SuppressWildcardExpansion = true; if (!InvokeProvider.Item.Exists(rp, false, _isLiteralPath)) @@ -858,7 +838,7 @@ protected override void ProcessRecord() InvokeProvider.SecurityDescriptor.Get(rp, sections, context); - sd = context.GetAccumulatedObjects(); + Collection sd = context.GetAccumulatedObjects(); if (sd != null) { AddBrokeredProperties( @@ -924,7 +904,7 @@ public string[] Path } } - private PSObject _inputObject = null; + private PSObject _inputObject; /// /// InputObject Parameter @@ -949,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 { @@ -965,7 +945,7 @@ public string[] LiteralPath } } - private bool _isLiteralPath = false; + private bool _isLiteralPath; private object _securityDescriptor; @@ -994,10 +974,7 @@ public object AclObject /// Parameter '-CentralAccessPolicy' is not supported in OneCore powershell, /// because function 'LsaQueryCAPs' is not available in OneCoreUAP and NanoServer. /// - private string CentralAccessPolicy - { - get; set; - } + private string CentralAccessPolicy { get; } #else private string centralAccessPolicy; @@ -1048,7 +1025,7 @@ public SwitchParameter ClearCentralAccessPolicy /// If true, the security descriptor is also passed /// down the output pipeline. /// - [Parameter()] + [Parameter] public SwitchParameter Passthru { get @@ -1066,7 +1043,7 @@ public SwitchParameter Passthru /// Returns a newly allocated SACL with no ACEs in it. /// Free the returned SACL by calling Marshal.FreeHGlobal. /// - private IntPtr GetEmptySacl() + private static IntPtr GetEmptySacl() { IntPtr pSacl = IntPtr.Zero; bool ret = true; @@ -1131,12 +1108,11 @@ private IntPtr GetSaclWithCapId(string capStr) // be deallocated separately (but with the entire buffer // returned by LsaQueryCAPs). freeCapId = false; - uint capCount = 0; rs = NativeMethods.LsaQueryCAPs( null, 0, out caps, - out capCount); + out uint capCount); if (rs != NativeMethods.STATUS_SUCCESS) { throw new Win32Exception((int)rs); @@ -1150,13 +1126,12 @@ private IntPtr GetSaclWithCapId(string capStr) } // Find the supplied string among available CAP names, use the corresponding CAPID. - NativeMethods.CENTRAL_ACCESS_POLICY cap = new NativeMethods.CENTRAL_ACCESS_POLICY(); IntPtr capPtr = caps; for (uint capIdx = 0; capIdx < capCount; capIdx++) { Dbg.Diagnostics.Assert(capPtr != IntPtr.Zero, "Invalid central access policies array"); - cap = Marshal.PtrToStructure(capPtr); + NativeMethods.CENTRAL_ACCESS_POLICY cap = Marshal.PtrToStructure(capPtr); // LSA_UNICODE_STRING is composed of WCHARs, but its length is given in bytes. string capName = Marshal.PtrToStringUni( cap.Name.Buffer, @@ -1252,7 +1227,7 @@ private IntPtr GetSaclWithCapId(string capStr) /// and the previous state of this privilege. Free the returned token /// by calling NativeMethods.CloseHandle. /// - private IntPtr GetTokenWithEnabledPrivilege( + private static IntPtr GetTokenWithEnabledPrivilege( string privilege, NativeMethods.TOKEN_PRIVILEGE previousState) { @@ -1286,7 +1261,7 @@ private IntPtr GetTokenWithEnabledPrivilege( } // Get the LUID of the specified privilege. - NativeMethods.LUID luid = new NativeMethods.LUID(); + NativeMethods.LUID luid = new(); ret = NativeMethods.LookupPrivilegeValue( null, privilege, @@ -1297,7 +1272,7 @@ private IntPtr GetTokenWithEnabledPrivilege( } // Enable the privilege. - NativeMethods.TOKEN_PRIVILEGE newState = new NativeMethods.TOKEN_PRIVILEGE(); + NativeMethods.TOKEN_PRIVILEGE newState = new(); newState.PrivilegeCount = 1; newState.Privilege.Attributes = NativeMethods.SE_PRIVILEGE_ENABLED; newState.Privilege.Luid = luid; @@ -1340,14 +1315,13 @@ protected override void ProcessRecord() if (methodInfo != null) { - CommonSecurityDescriptor aclCommonSD = _securityDescriptor as CommonSecurityDescriptor; string sddl; if (aclObjectSecurity != null) { sddl = aclObjectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.All); } - else if (aclCommonSD != null) + else if (_securityDescriptor is CommonSecurityDescriptor aclCommonSD) { sddl = aclCommonSD.GetSddlForm(AccessControlSections.All); } @@ -1442,7 +1416,7 @@ protected override void ProcessRecord() } IntPtr pSacl = IntPtr.Zero; - NativeMethods.TOKEN_PRIVILEGE previousState = new NativeMethods.TOKEN_PRIVILEGE(); + NativeMethods.TOKEN_PRIVILEGE previousState = new(); try { if (CentralAccessPolicy != null) @@ -1450,8 +1424,7 @@ protected override void ProcessRecord() pSacl = GetSaclWithCapId(CentralAccessPolicy); if (pSacl == IntPtr.Zero) { - SystemException e = new SystemException( - UtilsStrings.GetSaclWithCapIdFail); + SystemException e = new(UtilsStrings.GetSaclWithCapIdFail); WriteError(new ErrorRecord(e, "SetAcl_CentralAccessPolicy", ErrorCategory.InvalidResult, @@ -1464,8 +1437,7 @@ protected override void ProcessRecord() pSacl = GetEmptySacl(); if (pSacl == IntPtr.Zero) { - SystemException e = new SystemException( - UtilsStrings.GetEmptySaclFail); + SystemException e = new(UtilsStrings.GetEmptySaclFail); WriteError(new ErrorRecord(e, "SetAcl_ClearCentralAccessPolicy", ErrorCategory.InvalidResult, @@ -1476,15 +1448,14 @@ protected override void ProcessRecord() foreach (string p in Path) { - Collection pathsToProcess = new Collection(); + Collection pathsToProcess = new(); CmdletProviderContext context = this.CmdletProviderContext; context.PassThru = Passthru; if (_isLiteralPath) { - ProviderInfo Provider = null; - PSDriveInfo Drive = null; - string pathStr = SessionState.Path.GetUnresolvedProviderPathFromPSPath(p, out Provider, out Drive); + string pathStr = SessionState.Path.GetUnresolvedProviderPathFromPSPath( + p, out ProviderInfo Provider, out PSDriveInfo Drive); pathsToProcess.Add(new PathInfo(Drive, Provider, pathStr, SessionState)); context.SuppressWildcardExpansion = true; } @@ -1520,8 +1491,7 @@ protected override void ProcessRecord() IntPtr pToken = GetTokenWithEnabledPrivilege("SeSecurityPrivilege", previousState); if (pToken == IntPtr.Zero) { - SystemException e = new SystemException( - UtilsStrings.GetTokenWithEnabledPrivilegeFail); + SystemException e = new(UtilsStrings.GetTokenWithEnabledPrivilegeFail); WriteError(new ErrorRecord(e, "SetAcl_AdjustTokenPrivileges", ErrorCategory.InvalidResult, @@ -1542,7 +1512,7 @@ protected override void ProcessRecord() // Restore privileges to the previous state. if (pToken != IntPtr.Zero) { - NativeMethods.TOKEN_PRIVILEGE newState = new NativeMethods.TOKEN_PRIVILEGE(); + NativeMethods.TOKEN_PRIVILEGE newState = new(); uint newSize = 0; NativeMethods.AdjustTokenPrivileges( pToken, @@ -1594,4 +1564,3 @@ protected override void ProcessRecord() } #pragma warning restore 56506 - diff --git a/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs index 5d094413904..4d0cad2d3e9 100644 --- a/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CatalogCommands.cs @@ -4,14 +4,10 @@ #if !UNIX using System; +using System.Collections.ObjectModel; +using System.IO; using System.Management.Automation; using Dbg = System.Management.Automation.Diagnostics; -using System.Collections; -using System.IO; -using System.Management.Automation.Provider; -using System.Runtime.InteropServices; -using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.PowerShell.Commands { @@ -60,7 +56,7 @@ public string[] Path // // name of this command // - private string commandName; + private readonly string commandName; /// /// Initializes a new instance of the CatalogCommandsBase class, @@ -90,7 +86,7 @@ protected override void ProcessRecord() Dbg.Assert((CatalogFilePath != null) && (CatalogFilePath.Length > 0), "CatalogCommands: Param binder did not bind catalogFilePath"); - Collection paths = new Collection(); + Collection paths = new(); if (Path != null) { @@ -149,7 +145,7 @@ public NewFileCatalogCommand() : base("New-FileCatalog") { } /// /// Catalog version. /// - [Parameter()] + [Parameter] public int CatalogVersion { get @@ -164,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. @@ -186,7 +182,7 @@ protected override void PerformAction(Collection path, string catalogFil path.Add(SessionState.Path.CurrentFileSystemLocation.Path); } - FileInfo catalogFileInfo = new FileInfo(catalogFilePath); + FileInfo catalogFileInfo = new(catalogFilePath); // If Path points to the expected cat file make sure // parent Directory exists other wise CryptoAPI fails to create a .cat file @@ -227,7 +223,7 @@ public TestFileCatalogCommand() : base("Test-FileCatalog") { } /// /// - [Parameter()] + [Parameter] public SwitchParameter Detailed { get { return detailed; } @@ -240,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 28163845d22..e3a386ee507 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateCommands.cs @@ -1,11 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.IO; using System.Management.Automation; using System.Security; using System.Security.Cryptography; @@ -47,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 { @@ -80,7 +77,7 @@ public string[] LiteralPath // // list of files that were not found // - private List _filesNotFound = new List(); + private readonly List _filesNotFound = new(); /// /// Initializes a new instance of the GetPfxCertificateCommand @@ -108,7 +105,7 @@ protected override void ProcessRecord() foreach (string p in FilePath) { - List paths = new List(); + List paths = new(); // Expand wildcard characters if (_isLiteralPath) @@ -163,11 +160,11 @@ protected override void ProcessRecord() } catch (CryptographicException e) { - ErrorRecord er = - new ErrorRecord(e, - "GetPfxCertificateUnknownCryptoError", - ErrorCategory.NotSpecified, - null); + ErrorRecord er = new( + e, + "GetPfxCertificateUnknownCryptoError", + ErrorCategory.NotSpecified, + targetObject: null); WriteError(er); continue; } @@ -211,9 +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 3436ddb6062..37c687a7770 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -4,28 +4,29 @@ #if !UNIX 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.Management.Automation; using System.Management.Automation.Host; using System.Management.Automation.Internal; -using Runspaces = System.Management.Automation.Runspaces; -using Dbg = System.Management.Automation; -using Security = System.Management.Automation.Security; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections; -using System.Runtime.InteropServices; using System.Management.Automation.Provider; +using System.Runtime.InteropServices; +using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text.RegularExpressions; -using System.Globalization; -using System.IO; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Xml; using System.Xml.XPath; -using System.Security; + +using Dbg = System.Management.Automation; using DWORD = System.UInt32; +using Runspaces = System.Management.Automation.Runspaces; +using SMASecurity = System.Management.Automation.Security; namespace Microsoft.PowerShell.Commands { @@ -50,7 +51,7 @@ public SwitchParameter CodeSigningCert set { _codeSigningCert = value; } } - private SwitchParameter _codeSigningCert = new SwitchParameter(); + private SwitchParameter _codeSigningCert = new(); /// /// Gets or sets a filter that controls whether we only return @@ -122,17 +123,17 @@ public int ExpiringInDays /// The structure contains punycode name and unicode name. /// [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] - public struct DnsNameRepresentation + public readonly struct DnsNameRepresentation { /// /// Punycode version of DNS name. /// - private string _punycodeName; + private readonly string _punycodeName; /// /// Unicode version of DNS name. /// - private string _unicodeName; + private readonly string _unicodeName; /// /// Ambiguous constructor of a DnsNameRepresentation. @@ -213,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 + ")"; } } @@ -249,7 +250,7 @@ public SwitchParameter DeleteKey } } - private SwitchParameter _deleteKey = new SwitchParameter(); + private SwitchParameter _deleteKey = new(); } /// @@ -272,9 +273,9 @@ protected override bool ReleaseHandle() { bool fResult = false; - if (IntPtr.Zero != handle) + if (handle != IntPtr.Zero) { - fResult = Security.NativeMethods.CertCloseStore(handle, 0); + fResult = SMASecurity.NativeMethods.CertCloseStore(handle, 0); handle = IntPtr.Zero; } @@ -317,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: @@ -343,13 +344,13 @@ 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); - if (IntPtr.Zero == hCertStore) + if (hCertStore == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } @@ -363,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; @@ -390,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); } @@ -414,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 } @@ -441,12 +442,12 @@ public IntPtr GetCertByName(string Name) while (true) { certContext = GetNextCert(certContext); - if (IntPtr.Zero == certContext) + if (certContext == IntPtr.Zero) { break; } - X509Certificate2 cert = new X509Certificate2(certContext); + X509Certificate2 cert = new(certContext); if (string.Equals( cert.Thumbprint, Name, @@ -463,13 +464,12 @@ public IntPtr GetCertByName(string Name) public void FreeCert(IntPtr certContext) { - Security.NativeMethods.CertFreeCertificateContext(certContext); + SMASecurity.NativeMethods.CertFreeCertificateContext(certContext); } /// /// Native IntPtr store handle. /// - public IntPtr StoreHandle { get @@ -503,7 +503,6 @@ public string StoreName /// /// True if a real store is open. /// - public bool Valid { get @@ -513,8 +512,8 @@ public bool Valid } private bool _archivedCerts = false; - private X509StoreLocation _storeLocation = null; - private string _storeName = null; + private readonly X509StoreLocation _storeLocation = null; + private readonly string _storeName = null; private CertificateStoreHandle _storeHandle = null; private bool _valid = false; private bool _open = false; @@ -557,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 @@ -583,7 +583,7 @@ public sealed class CertificateProvider : NavigationCmdletProvider, ICmdletProvi /// -- storeLocations /// -- pathCache. /// - private static object s_staticLock = new object(); + private static readonly object s_staticLock = new(); /// /// List of store locations. They do not change once initialized. @@ -636,7 +636,7 @@ private static Regex CertPathRegex { if (s_certPathRegex == null) { - RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Compiled; + const RegexOptions options = RegexOptions.IgnoreCase | RegexOptions.Compiled; s_certPathRegex = new Regex(certPathPattern, options); } } @@ -665,8 +665,7 @@ public CertificateProvider() // // create and cache CurrentUser store-location // - X509StoreLocation user = - new X509StoreLocation(StoreLocation.CurrentUser); + X509StoreLocation user = new(StoreLocation.CurrentUser); s_storeLocations.Add(user); AddItemToCache(nameof(StoreLocation.CurrentUser), user); @@ -674,8 +673,7 @@ public CertificateProvider() // // create and cache LocalMachine store-location // - X509StoreLocation machine = - new X509StoreLocation(StoreLocation.LocalMachine); + X509StoreLocation machine = new(StoreLocation.LocalMachine); s_storeLocations.Add(machine); AddItemToCache(nameof(StoreLocation.LocalMachine), machine); @@ -701,7 +699,6 @@ public CertificateProvider() /// path is null or empty. /// destination is null or empty. /// - protected override void RemoveItem( string path, bool recurse) @@ -723,20 +720,15 @@ protected override void RemoveItem( string.Equals(pathElements[1], "ROOT", StringComparison.OrdinalIgnoreCase)) { string message = CertificateProviderStrings.UINotAllowed; - string errorId = "UINotAllowed"; + const string errorId = "UINotAllowed"; 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; } } @@ -748,7 +740,7 @@ protected override void RemoveItem( if (fUserContext) { string message = CertificateProviderStrings.CannotDeleteUserStore; - string errorId = "CannotDeleteUserStore"; + const string errorId = "CannotDeleteUserStore"; ThrowInvalidOperation(errorId, message); } @@ -758,7 +750,7 @@ protected override void RemoveItem( else // other container than a store { string message = CertificateProviderStrings.CannotRemoveContainer; - string errorId = "CannotRemoveContainer"; + const string errorId = "CannotRemoveContainer"; ThrowInvalidOperation(errorId, message); } } @@ -808,7 +800,6 @@ protected override object RemoveItemDynamicParameters(string path, bool recurse) /// path is null or empty. /// destination is null or empty. /// - protected override void MoveItem( string path, string destination) @@ -830,7 +821,7 @@ protected override void MoveItem( if (isContainer) { string message = CertificateProviderStrings.CannotMoveContainer; - string errorId = "CannotMoveContainer"; + const string errorId = "CannotMoveContainer"; ThrowInvalidOperation(errorId, message); } @@ -847,7 +838,7 @@ protected override void MoveItem( else { string message = CertificateProviderStrings.InvalidDestStore; - string errorId = "InvalidDestStore"; + const string errorId = "InvalidDestStore"; ThrowInvalidOperation(errorId, message); } } @@ -859,14 +850,14 @@ protected override void MoveItem( if (!string.Equals(pathElements[0], destElements[0], StringComparison.OrdinalIgnoreCase)) { string message = CertificateProviderStrings.CannotMoveCrossContext; - string errorId = "CannotMoveCrossContext"; + const string errorId = "CannotMoveCrossContext"; ThrowInvalidOperation(errorId, message); } if (string.Equals(pathElements[1], destElements[1], StringComparison.OrdinalIgnoreCase)) { string message = CertificateProviderStrings.CannotMoveToSameStore; - string errorId = "CannotMoveToSameStore"; + const string errorId = "CannotMoveToSameStore"; ThrowInvalidOperation(errorId, message); } @@ -880,7 +871,7 @@ protected override void MoveItem( string.Equals(destElements[1], "ROOT", StringComparison.OrdinalIgnoreCase))) { string message = CertificateProviderStrings.UINotAllowed; - string errorId = "UINotAllowed"; + const string errorId = "UINotAllowed"; ThrowInvalidOperation(errorId, message); } } @@ -892,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); @@ -930,7 +920,7 @@ protected override void MoveItem( /// /// The path of the certificate store to create. /// - /// + /// /// Ignored. /// Only support store. /// @@ -964,7 +954,7 @@ protected override void NewItem( if (pathElements.Length != 2) { string message = CertificateProviderStrings.CannotCreateItem; - string errorId = "CannotCreateItem"; + const string errorId = "CannotCreateItem"; ThrowInvalidOperation(errorId, message); } @@ -974,35 +964,33 @@ protected override void NewItem( if (fUserContext) { string message = CertificateProviderStrings.CannotCreateUserStore; - string errorId = "CannotCreateUserStore"; + const string errorId = "CannotCreateUserStore"; ThrowInvalidOperation(errorId, message); } - 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]); - if (IntPtr.Zero == hCertStore) + if (hCertStore == IntPtr.Zero) { throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); } else // free native store handle { bool fResult = false; - fResult = Security.NativeMethods.CertCloseStore(hCertStore, 0); + fResult = SMASecurity.NativeMethods.CertCloseStore(hCertStore, 0); } - X509Store outStore = new X509Store( - pathElements[1], - StoreLocation.LocalMachine); + X509Store outStore = new(pathElements[1], StoreLocation.LocalMachine); WriteItemObject(outStore, path, true); } @@ -1019,15 +1007,14 @@ protected override Collection InitializeDefaultDrives() { string providerDescription = CertificateProviderStrings.CertProvidername; - PSDriveInfo drive = - new PSDriveInfo( - "Cert", // drive name - ProviderInfo,// provider name - @"\", // root path - providerDescription, - null); + PSDriveInfo drive = new( + name: "Cert", + provider: ProviderInfo, + root: @"\", + providerDescription, + credential: null); - Collection drives = new Collection(); + Collection drives = new(); drives.Add(drive); return drives; @@ -1075,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 (IntPtr.Zero != certContext) - { - store.FreeCert(certContext); - result = true; - } + store.FreeCert(certContext); + result = true; } } } @@ -1195,7 +1177,7 @@ protected override bool ItemExists(string path) // If the inner exception is not of that type // then we need to rethrow // - if (!(e.InnerException is CertificateProviderItemNotFoundException)) + if (e.InnerException is not CertificateProviderItemNotFoundException) { throw; } @@ -1266,22 +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 X509Store( - store.StoreName, - store.Location.Location); - WriteItemObject(outStore, path, isContainer); - } + // create X509Store + X509Store outStore = new(store.StoreName, store.Location.Location); + WriteItemObject(outStore, path, isContainer); } } } @@ -1352,7 +1327,7 @@ private void AttemptToImportPkiModule() new CmdletInfo( "Import-Module", typeof(Microsoft.PowerShell.Commands.ImportModuleCommand)); - Runspaces.Command importModuleCommand = new Runspaces.Command(commandInfo); + Runspaces.Command importModuleCommand = new(commandInfo); s_tracer.WriteLine("Attempting to load module: {0}", moduleName); @@ -1377,7 +1352,7 @@ private void AttemptToImportPkiModule() _hasAttemptedToLoadPkiModule = true; } - private string MyGetChildName(string path) + private static string MyGetChildName(string path) { // Verify the parameters @@ -1418,7 +1393,7 @@ protected override void InvokeDefaultAction(string path) { path = NormalizePath(path); string action = CertificateProviderStrings.Action_Invoke; - string certmgr = "certmgr.msc"; + const string certmgr = "certmgr.msc"; string certPath = System.IO.Path.Combine( System.Environment.ExpandEnvironmentVariables("%windir%"), "system32"); @@ -1485,7 +1460,7 @@ private static ErrorRecord CreateErrorRecord(string path, message = string.Format( System.Globalization.CultureInfo.CurrentCulture, message, path); - ErrorDetails ed = new ErrorDetails(message); + ErrorDetails ed = new(message); // // create appropriate exception type @@ -1509,11 +1484,11 @@ private static ErrorRecord CreateErrorRecord(string path, break; } - ErrorRecord er = - new ErrorRecord(e, - "CertProviderItemNotFound", - ErrorCategory.ObjectNotFound, - null); + ErrorRecord er = new( + e, + "CertProviderItemNotFound", + ErrorCategory.ObjectNotFound, + targetObject: null); er.ErrorDetails = ed; @@ -1529,7 +1504,7 @@ private void ThrowErrorRemoting(int stat) string message = CertificateProviderStrings.RemoteErrorMessage; error += message; - Exception e2 = new Exception(error); + Exception e2 = new(error); ThrowTerminatingError( new ErrorRecord( e2, @@ -1545,11 +1520,11 @@ private void ThrowErrorRemoting(int stat) private void ThrowInvalidOperation(string errorId, string message) { - ErrorRecord errorRecord = new ErrorRecord( - new InvalidOperationException(message), - errorId, - ErrorCategory.InvalidOperation, - null); + ErrorRecord errorRecord = new( + new InvalidOperationException(message), + errorId, + ErrorCategory.InvalidOperation, + targetObject: null); errorRecord.ErrorDetails = new ErrorDetails(message); ThrowTerminatingError(errorRecord); @@ -1577,7 +1552,7 @@ private static string NormalizePath(string path) string[] elts = GetPathElements(path); - path = string.Join("\\", elts); + path = string.Join('\\', elts); } return path; @@ -1588,7 +1563,7 @@ private static string[] GetPathElements(string path) string[] allElts = path.Split(s_pathSeparators); string[] result = null; - Stack elts = new Stack(); + Stack elts = new(); foreach (string e in allElts) { @@ -1620,14 +1595,13 @@ private static string[] GetPathElements(string path) /// /// Key prov info. /// No return. - [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Management.Automation.Security.NativeMethods.NCryptSetProperty(System.IntPtr,System.String,System.Void*,System.Int32,System.Int32)")] [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Management.Automation.Security.NativeMethods.NCryptFreeObject(System.IntPtr)")] 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); @@ -1635,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()); } @@ -1674,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); @@ -1697,7 +1671,7 @@ private void DoDeleteKey(IntPtr pProvInfo) ThrowErrorRemoting(stat); } - stat = Security.NativeMethods.NCryptOpenKey( + stat = SMASecurity.NativeMethods.NCryptOpenKey( hCNGProv, ref hCNGKey, keyProvInfo.pwszContainerName, @@ -1708,21 +1682,21 @@ private void DoDeleteKey(IntPtr pProvInfo) ThrowErrorRemoting(stat); } - if (0 != (cngKeyFlag & (uint)Security.NativeMethods.NCryptDeletKeyFlag.NCRYPT_SILENT_FLAG)) + 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); @@ -1733,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); } } } @@ -1749,13 +1723,12 @@ private void DoDeleteKey(IntPtr pProvInfo) /// Boolean to specify whether or not to delete private key. /// Source path. /// No return. - 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 (IntPtr.Zero == localName)//not find, we can remove + if (localName == IntPtr.Zero)//not find, we can remove { X509NativeStore store = null; @@ -1769,26 +1742,26 @@ private void RemoveCertStore(string storeName, bool fDeleteKey, string sourcePat // enumerate over each cert and remove it // IntPtr certContext = store.GetFirstCert(); - while (IntPtr.Zero != certContext) + while (certContext != IntPtr.Zero) { - X509Certificate2 cert = new X509Certificate2(certContext); + X509Certificate2 cert = new(certContext); string certPath = sourcePath + cert.Thumbprint; RemoveCertItem(cert, fDeleteKey, true, certPath); certContext = store.GetNextCert(certContext); } // remove the cert store - 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); @@ -1799,7 +1772,7 @@ private void RemoveCertStore(string storeName, bool fDeleteKey, string sourcePat CultureInfo.CurrentCulture, CertificateProviderStrings.RemoveStoreTemplate, storeName); - string errorId = "CannotRemoveSystemStore"; + const string errorId = "CannotRemoveSystemStore"; ThrowInvalidOperation(errorId, message); } } @@ -1847,7 +1820,6 @@ private void RemoveCertItem(X509Certificate2 cert, bool fDeleteKey, bool fMachin /// Machine context or user. /// Source path. /// No return. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults")] private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, string sourcePath) { @@ -1861,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)) { @@ -1891,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()); } @@ -1900,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); } @@ -1926,12 +1898,12 @@ private void DoRemove(X509Certificate2 cert, bool fDeleteKey, bool fMachine, str /// /// An IntPtr for store handle. /// No return. - private void CommitUserDS(IntPtr storeHandle) + 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()); @@ -1953,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) { @@ -1961,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()); } @@ -1986,13 +1958,13 @@ 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); } // get the output object - X509Certificate2 outObj = new X509Certificate2(outCert); + X509Certificate2 outObj = new(outCert); string certName = GetCertName(outObj); string certPath = MakePath(destination, certName); WriteItemObject((object)outObj, certPath, false); @@ -2023,16 +1995,14 @@ private object GetItemAtPath(string path, bool test, out bool isContainer) // // Thus lengths 1 & 2 are container items. // - isContainer = (pathElements.Length >= 0) && - (pathElements.Length <= 2); + isContainer = pathElements.Length <= 2; X509NativeStore store = null; // // handle invalid path depth // - if ((pathElements.Length > 3) || - (pathElements.Length < 0)) + if (pathElements.Length > 3) { if (test) { @@ -2105,7 +2075,7 @@ private object GetItemAtPath(string path, bool test, out bool isContainer) store.Open(IncludeArchivedCerts()); IntPtr certContext = store.GetCertByName(pathElements[2]); - if (IntPtr.Zero == certContext) + if (certContext == IntPtr.Zero) { if (test) { @@ -2456,9 +2426,9 @@ private void GetCertificatesOrNames(string path, // IntPtr certContext = store.GetFirstCert(); - while (IntPtr.Zero != certContext) + while (certContext != IntPtr.Zero) { - X509Certificate2 cert = new X509Certificate2(certContext); + X509Certificate2 cert = new(certContext); if (MatchesFilter(cert, filter)) { @@ -2471,7 +2441,7 @@ private void GetCertificatesOrNames(string path, } else { - PSObject myPsObj = new PSObject(cert); + PSObject myPsObj = new(cert); thingToReturn = (object)myPsObj; } @@ -2560,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; } @@ -2601,9 +2568,7 @@ private void GetStoresOrNames( else { X509NativeStore store = GetStore(storePath, name, location); - X509Store ManagedStore = new X509Store( - store.StoreName, - store.Location.Location); + X509Store ManagedStore = new(store.StoreName, store.Location.Location); thingToReturn = ManagedStore; } @@ -2663,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 = filter ?? new CertificateFilterInfo(); - filter.Purpose = CertificatePurpose.DocumentEncryption; - } + if (dp.DocumentEncryptionCert) + { + filter ??= new CertificateFilterInfo(); + filter.Purpose = CertificatePurpose.DocumentEncryption; + } - if (dp.DnsName != null) - { - filter = 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 = 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 = 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 = filter ?? new CertificateFilterInfo(); - filter.SSLServerAuthentication = true; - } + if (dp.SSLServerAuthentication) + { + filter ??= new CertificateFilterInfo(); + filter.SSLServerAuthentication = true; } } @@ -2834,7 +2794,7 @@ internal static bool CertContainsEku(X509Certificate2 cert, List /// Gets the location as a - /// + /// /// public StoreLocation Location { @@ -3070,22 +3030,21 @@ public X509StoreLocation(StoreLocation location) /// The structure contains friendly name and EKU oid. /// [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] - public struct EnhancedKeyUsageRepresentation + public readonly struct EnhancedKeyUsageRepresentation { /// /// Localized friendly name of EKU. /// - private string _friendlyName; + private readonly string _friendlyName; /// /// OID of EKU. /// - private string _oid; + private readonly string _oid; /// /// Constructor of an EnhancedKeyUsageRepresentation. /// - [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Oid")] public EnhancedKeyUsageRepresentation(string inputFriendlyName, string inputOid) @@ -3154,14 +3113,12 @@ public override string ToString() /// /// Class for SendAsTrustedIssuer. /// - [SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors")] public sealed class SendAsTrustedIssuerProperty { /// /// Get property of SendAsTrustedIssuer. /// - [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public static bool ReadSendAsTrustedIssuerProperty(X509Certificate2 cert) { @@ -3171,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)) { @@ -3184,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); } @@ -3197,14 +3154,13 @@ public static bool ReadSendAsTrustedIssuerProperty(X509Certificate2 cert) /// /// Set property of SendAsTrustedIssuer. /// - [SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")] public static void WriteSendAsTrustedIssuerProperty(X509Certificate2 cert, string certPath, bool addProperty) { if (DownLevelHelper.TrustedIssuerSupported()) { IntPtr propertyPtr = IntPtr.Zero; - Security.NativeMethods.CRYPT_DATA_BLOB dataBlob = new Security.NativeMethods.CRYPT_DATA_BLOB(); + SMASecurity.NativeMethods.CRYPT_DATA_BLOB dataBlob = new(); dataBlob.cbData = 0; dataBlob.pbData = IntPtr.Zero; X509Certificate certFromStore = null; @@ -3227,7 +3183,7 @@ public static void WriteSendAsTrustedIssuerProperty(X509Certificate2 cert, strin bool fUserContext = string.Equals(pathElements[1], "Certificate::CurrentUser", StringComparison.OrdinalIgnoreCase); X509StoreLocation storeLocation = - new X509StoreLocation(fUserContext ? StoreLocation.CurrentUser : StoreLocation.LocalMachine); + new(fUserContext ? StoreLocation.CurrentUser : StoreLocation.LocalMachine); // get certificate from the store pathElements[2] X509NativeStore store = null; @@ -3251,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)) { @@ -3270,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); } } @@ -3281,7 +3237,7 @@ private static string[] GetPathElements(string path) string[] allElts = path.Split(s_separators); string[] result = null; - Stack elts = new Stack(); + Stack elts = new(); foreach (string e in allElts) { @@ -3311,10 +3267,9 @@ private static string[] GetPathElements(string path) /// /// Class for ekulist. /// - public sealed class EnhancedKeyUsageProperty { - private List _ekuList = new List(); + private readonly List _ekuList = new(); /// /// Get property of EKUList. @@ -3335,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 EnhancedKeyUsageRepresentation(oid.FriendlyName, oid.Value); - _ekuList.Add(ekuString); - } + EnhancedKeyUsageRepresentation ekuString = new(oid.FriendlyName, oid.Value); + _ekuList.Add(ekuString); } } } @@ -3355,24 +3306,33 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) /// /// Class for DNSNameList. /// - public sealed class DnsNameProperty { - private List _dnsList = new List(); - private System.Globalization.IdnMapping idnMapping = new System.Globalization.IdnMapping(); + private readonly List _dnsList = 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 + { + unicodeName = idnMapping.GetUnicode(dnsName); + } + catch (ArgumentException) { - return _dnsList; + // The name is not valid Punycode, assume it's valid ASCII. + unicodeName = dnsName; } + + return new DnsNameRepresentation(dnsName, unicodeName); } /// @@ -3380,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); } } } @@ -3493,7 +3424,6 @@ internal static IntPtr GetOwnerWindow(PSHost host) return IntPtr.Zero; } #else - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr hWnd = IntPtr.Zero; private static bool firstRun = true; @@ -3509,12 +3439,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(); } } } @@ -3528,8 +3458,7 @@ private static bool IsUIAllowed(PSHost host) return false; uint SessionId; - uint ProcessId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; - if (!Security.NativeMethods.ProcessIdToSessionId(ProcessId, out SessionId)) + if (!SMASecurity.NativeMethods.ProcessIdToSessionId((uint)Environment.ProcessId, out SessionId)) return false; if (SessionId == 0) @@ -3565,27 +3494,26 @@ internal static class Crypt32Helpers /// Lock that guards access to the following static members /// -- storeNames. /// - private static object s_staticLock = new object(); + private static readonly object s_staticLock = new(); - internal static readonly List storeNames = new List(); + internal static readonly List storeNames = new(); /// /// 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: @@ -3593,17 +3521,16 @@ internal static List GetStoreNamesAtLocation(StoreLocation location) break; } - Security.NativeMethods.CertEnumSystemStoreCallBackProto callBack = - new Security.NativeMethods.CertEnumSystemStoreCallBackProto(CertEnumSystemStoreCallBack); + SMASecurity.NativeMethods.CertEnumSystemStoreCallBackProto callBack = new(CertEnumSystemStoreCallBack); // Return a new list to avoid synchronization issues. - List names = new List(); + List names = new(); lock (s_staticLock) { 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 4c90c02e941..5e8e6d19b46 100644 --- a/src/Microsoft.PowerShell.Security/security/CmsCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/CmsCommands.cs @@ -35,15 +35,15 @@ 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; set; } - private PSDataCollection _inputObjects = new PSDataCollection(); + private readonly PSDataCollection _inputObjects = new(); /// /// Gets or sets the content of the CMS Message by path. @@ -94,11 +94,15 @@ protected override void BeginProcessing() if ((resolvedPaths.Count > 1) || (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase))) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - CmsCommands.FilePathMustBeFileSystemPath, Path)), - "FilePathMustBeFileSystemPath", ErrorCategory.ObjectNotFound, provider); + string.Format( + CultureInfo.InvariantCulture, + CmsCommands.FilePathMustBeFileSystemPath, + Path)), + "FilePathMustBeFileSystemPath", + ErrorCategory.ObjectNotFound, + provider); ThrowTerminatingError(error); } @@ -141,7 +145,7 @@ protected override void EndProcessing() if (_inputObjects.Count > 0) { - StringBuilder outputString = new StringBuilder(); + StringBuilder outputString = new(); Collection output = System.Management.Automation.PowerShell.Create() .AddCommand("Microsoft.PowerShell.Utility\\Out-String") @@ -198,15 +202,15 @@ 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; set; } - private StringBuilder _contentBuffer = new StringBuilder(); + private readonly StringBuilder _contentBuffer = new(); /// /// Gets or sets the CMS Message by path. @@ -245,11 +249,15 @@ protected override void BeginProcessing() if ((resolvedPaths.Count > 1) || (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase))) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - CmsCommands.FilePathMustBeFileSystemPath, Path)), - "FilePathMustBeFileSystemPath", ErrorCategory.ObjectNotFound, provider); + string.Format( + CultureInfo.InvariantCulture, + CmsCommands.FilePathMustBeFileSystemPath, + Path)), + "FilePathMustBeFileSystemPath", + ErrorCategory.ObjectNotFound, + provider); ThrowTerminatingError(error); } @@ -300,21 +308,20 @@ 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 ErrorRecord( + ErrorRecord error = new( new ArgumentException(CmsCommands.InputContainedNoEncryptedContent), "InputContainedNoEncryptedContent", ErrorCategory.ObjectNotFound, null); ThrowTerminatingError(error); } - EnvelopedCms cms = new EnvelopedCms(); + EnvelopedCms cms = new(); cms.Decode(contentBytes); - PSObject result = new PSObject(cms); - List recipients = new List(); + PSObject result = new(cms); + List recipients = new(); foreach (RecipientInfo recipient in cms.RecipientInfos) { recipients.Add(recipient.RecipientIdentifier.Value); @@ -343,15 +350,15 @@ 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; set; } - private StringBuilder _contentBuffer = new StringBuilder(); + private readonly StringBuilder _contentBuffer = new(); /// /// Gets or sets the Windows Event Log Message with contents to be decrypted. @@ -390,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; @@ -422,11 +429,15 @@ protected override void BeginProcessing() if ((resolvedPaths.Count > 1) || (!string.Equals(provider.Name, "FileSystem", StringComparison.OrdinalIgnoreCase))) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - CmsCommands.FilePathMustBeFileSystemPath, Path)), - "FilePathMustBeFileSystemPath", ErrorCategory.ObjectNotFound, provider); + string.Format( + CultureInfo.InvariantCulture, + CmsCommands.FilePathMustBeFileSystemPath, + Path)), + "FilePathMustBeFileSystemPath", + ErrorCategory.ObjectNotFound, + provider); ThrowTerminatingError(error); } @@ -512,11 +523,15 @@ private string Decrypt(string actualContent) byte[] messageBytes = CmsUtils.RemoveAsciiArmor(actualContent, CmsUtils.BEGIN_CMS_SIGIL, CmsUtils.END_CMS_SIGIL, out startIndex, out endIndex); if ((messageBytes == null) && (!IncludeContext)) { - ErrorRecord error = new ErrorRecord( + ErrorRecord error = new( new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - CmsCommands.InputContainedNoEncryptedContentIncludeContext, "-IncludeContext")), - "InputContainedNoEncryptedContentIncludeContext", ErrorCategory.ObjectNotFound, null); + string.Format( + CultureInfo.InvariantCulture, + CmsCommands.InputContainedNoEncryptedContentIncludeContext, + "-IncludeContext")), + "InputContainedNoEncryptedContentIncludeContext", + ErrorCategory.ObjectNotFound, + targetObject: null); ThrowTerminatingError(error); } @@ -536,8 +551,8 @@ private string Decrypt(string actualContent) } } - EnvelopedCms cms = new EnvelopedCms(); - X509Certificate2Collection certificates = new X509Certificate2Collection(); + EnvelopedCms cms = new(); + X509Certificate2Collection certificates = new(); if ((To != null) && (To.Length > 0)) { @@ -577,7 +592,7 @@ private string Decrypt(string actualContent) if (postContext != null) { - resultString = resultString + postContext; + resultString += postContext; } } @@ -585,4 +600,3 @@ private string Decrypt(string actualContent) } } } - diff --git a/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs b/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs index cb23979c77f..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; } @@ -104,7 +104,11 @@ protected override void BeginProcessing() } catch (ArgumentException exception) { - ErrorRecord errorRecord = new ErrorRecord(exception, "CouldNotPromptForCredential", ErrorCategory.InvalidOperation, null); + ErrorRecord errorRecord = new( + exception, + "CouldNotPromptForCredential", + ErrorCategory.InvalidOperation, + targetObject: null); WriteError(errorRecord); } diff --git a/src/Microsoft.PowerShell.Security/security/ExecutionPolicyCommands.cs b/src/Microsoft.PowerShell.Security/security/ExecutionPolicyCommands.cs index 7924fcd7652..dc526425c74 100644 --- a/src/Microsoft.PowerShell.Security/security/ExecutionPolicyCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/ExecutionPolicyCommands.cs @@ -65,11 +65,11 @@ protected override void BeginProcessing() { string message = ExecutionPolicyCommands.ListAndScopeSpecified; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(), "ListAndScopeSpecified", ErrorCategory.InvalidOperation, - null); + targetObject: null); errorRecord.ErrorDetails = new ErrorDetails(message); ThrowTerminatingError(errorRecord); @@ -83,11 +83,10 @@ protected override void BeginProcessing() { foreach (ExecutionPolicyScope scope in SecuritySupport.ExecutionPolicyScopePreferences) { - PSObject outputObject = new PSObject(); + PSObject outputObject = new(); ExecutionPolicy policy = SecuritySupport.GetExecutionPolicy(shellId, scope); - PSNoteProperty inputNote = new PSNoteProperty( - "Scope", scope); + PSNoteProperty inputNote = new("Scope", scope); outputObject.Properties.Add(inputNote); inputNote = new PSNoteProperty( "ExecutionPolicy", policy); @@ -176,11 +175,11 @@ protected override void BeginProcessing() { string message = ExecutionPolicyCommands.CantSetGroupPolicy; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new InvalidOperationException(), "CantSetGroupPolicy", ErrorCategory.InvalidOperation, - null); + targetObject: null); errorRecord.ErrorDetails = new ErrorDetails(message); ThrowTerminatingError(errorRecord); @@ -221,11 +220,11 @@ protected override void ProcessRecord() string message = StringUtil.Format(ExecutionPolicyCommands.ExecutionPolicyOverridden, effectiveExecutionPolicy); string recommendedAction = ExecutionPolicyCommands.ExecutionPolicyOverriddenRecommendedAction; - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( new System.Security.SecurityException(), "ExecutionPolicyOverride", ErrorCategory.PermissionDenied, - null); + targetObject: null); errorRecord.ErrorDetails = new ErrorDetails(message); errorRecord.ErrorDetails.RecommendedAction = recommendedAction; @@ -329,11 +328,11 @@ private bool IsProcessInteractive() private void OnAccessDeniedError(Exception exception) { string message = StringUtil.Format(ExecutionPolicyCommands.SetExecutionPolicyAccessDeniedError, exception.Message); - ErrorRecord errorRecord = new ErrorRecord( + ErrorRecord errorRecord = new( exception, exception.GetType().FullName, ErrorCategory.PermissionDenied, - null); + targetObject: null); errorRecord.ErrorDetails = new ErrorDetails(message); ThrowTerminatingError(errorRecord); diff --git a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs index 74dee890cdf..29179a7046d 100644 --- a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs @@ -2,8 +2,6 @@ // Licensed under the MIT License. using System; -using System.Globalization; -using System.IO; using System.Management.Automation; using System.Runtime.InteropServices; using System.Security; @@ -35,7 +33,7 @@ protected SecureString SecureStringData // // name of this command // - private string _commandName; + private readonly string _commandName; /// /// Initializes a new instance of the SecureStringCommandBase @@ -241,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 { @@ -313,8 +311,7 @@ protected override void ProcessRecord() byte[] iv = null; // If this is a V2 package - if (String.IndexOf(SecureStringHelper.SecureStringExportHeader, - StringComparison.OrdinalIgnoreCase) == 0) + if (String.StartsWith(SecureStringHelper.SecureStringExportHeader, StringComparison.OrdinalIgnoreCase)) { try { @@ -328,7 +325,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) { @@ -361,8 +358,7 @@ protected override void ProcessRecord() } else { - importedString = new SecureString(); - foreach (char currentChar in String) { importedString.AppendChar(currentChar); } + importedString = SecureStringHelper.FromPlainTextString(String); } } catch (ArgumentException e) @@ -391,4 +387,3 @@ protected override void ProcessRecord() } } } - diff --git a/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs b/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs index 91a3e8e3a83..53a662ca4f9 100644 --- a/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SignatureCommands.cs @@ -2,22 +2,15 @@ // Licensed under the MIT License. using System; -using System.Collections; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Management.Automation; using System.Management.Automation.Internal; -using System.Management.Automation.Provider; -using System.Runtime.InteropServices; -using System.Security; using System.Security.Cryptography.X509Certificates; using Dbg = System.Management.Automation.Diagnostics; -using DWORD = System.UInt32; - namespace Microsoft.PowerShell.Commands { /// @@ -51,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 { @@ -111,7 +104,10 @@ public string[] SourcePathOrExtension [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public byte[] Content { - get { return _content; } + get + { + return _content; + } set { @@ -124,7 +120,7 @@ public byte[] Content // // name of this command // - private string _commandName; + private readonly string _commandName; /// /// Initializes a new instance of the SignatureCommandsBase class, @@ -162,7 +158,7 @@ protected override void ProcessRecord() foreach (string p in FilePath) { - Collection paths = new Collection(); + Collection paths = new(); // Expand wildcard characters if (_isLiteralPath) @@ -298,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); } } @@ -378,10 +374,7 @@ public string TimestampServer set { - if (value == null) - { - value = string.Empty; - } + value ??= string.Empty; _timestampServer = value; } @@ -408,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 @@ -471,7 +464,7 @@ protected override Signature PerformAction(string filePath) try { // remove readonly attributes on the file - FileInfo fInfo = new FileInfo(filePath); + FileInfo fInfo = new(filePath); if (fInfo != null) { // Save some disk write time by checking whether file is readonly.. @@ -487,56 +480,51 @@ protected override Signature PerformAction(string filePath) // These are the known exceptions for File.Load and StreamWriter.ctor catch (ArgumentException e) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( e, "ForceArgumentException", ErrorCategory.WriteError, - filePath - ); + filePath); WriteError(er); return null; } catch (IOException e) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( e, "ForceIOException", ErrorCategory.WriteError, - filePath - ); + filePath); WriteError(er); return null; } catch (UnauthorizedAccessException e) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( e, "ForceUnauthorizedAccessException", ErrorCategory.PermissionDenied, - filePath - ); + filePath); WriteError(er); return null; } catch (NotSupportedException e) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( e, "ForceNotSupportedException", ErrorCategory.WriteError, - filePath - ); + filePath); WriteError(er); return null; } catch (System.Security.SecurityException e) { - ErrorRecord er = new ErrorRecord( + ErrorRecord er = new( e, "ForceSecurityException", ErrorCategory.PermissionDenied, - filePath - ); + filePath); WriteError(er); return null; } @@ -555,7 +543,7 @@ protected override Signature PerformAction(string filePath) System.Globalization.CultureInfo.CurrentCulture, UtilsStrings.FileSmallerThan4Bytes, filePath); - PSArgumentException e = new PSArgumentException(message, nameof(filePath)); + PSArgumentException e = new(message, nameof(filePath)); ErrorRecord er = SecurityUtils.CreateInvalidArgumentErrorRecord( e, "SignatureCommandsBaseFileSmallerThan4Bytes" diff --git a/src/Microsoft.PowerShell.Security/security/Utils.cs b/src/Microsoft.PowerShell.Security/security/Utils.cs index f8eb7243285..702b44a8518 100644 --- a/src/Microsoft.PowerShell.Security/security/Utils.cs +++ b/src/Microsoft.PowerShell.Security/security/Utils.cs @@ -21,7 +21,7 @@ internal static long GetFileSize(string filePath) { long size = 0; - using (FileStream fs = new FileStream(filePath, FileMode.Open)) + using (FileStream fs = new(filePath, FileMode.Open)) { size = fs.Length; } @@ -64,14 +64,13 @@ ErrorRecord CreateFileNotFoundErrorRecord(string resourceStr, args ); - FileNotFoundException e = - new FileNotFoundException(message); + FileNotFoundException e = new(message); - ErrorRecord er = - new ErrorRecord(e, - errorId, - ErrorCategory.ObjectNotFound, - null); + ErrorRecord er = new( + e, + errorId, + ErrorCategory.ObjectNotFound, + targetObject: null); return er; } @@ -85,14 +84,13 @@ internal static ErrorRecord CreatePathNotFoundErrorRecord(string path, string errorId) { - ItemNotFoundException e = - new ItemNotFoundException(path, "PathNotFound", SessionStateStrings.PathNotFound); + ItemNotFoundException e = new(path, "PathNotFound", SessionStateStrings.PathNotFound); - ErrorRecord er = - new ErrorRecord(e, - errorId, - ErrorCategory.ObjectNotFound, - null); + ErrorRecord er = new( + e, + errorId, + ErrorCategory.ObjectNotFound, + targetObject: null); return er; } @@ -111,14 +109,13 @@ ErrorRecord CreateNotSupportedErrorRecord(string resourceStr, { string message = StringUtil.Format(resourceStr, args); - NotSupportedException e = - new NotSupportedException(message); + NotSupportedException e = new(message); - ErrorRecord er = - new ErrorRecord(e, - errorId, - ErrorCategory.NotImplemented, - null); + ErrorRecord er = new( + e, + errorId, + ErrorCategory.NotImplemented, + targetObject: null); return er; } @@ -133,11 +130,11 @@ internal static ErrorRecord CreateInvalidArgumentErrorRecord(Exception e, string errorId) { - ErrorRecord er = - new ErrorRecord(e, - errorId, - ErrorCategory.InvalidArgument, - null); + ErrorRecord er = new( + e, + errorId, + ErrorCategory.InvalidArgument, + targetObject: null); return er; } 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 326f53d4d94..55141379faa 100644 --- a/src/Microsoft.WSMan.Management/ConfigProvider.cs +++ b/src/Microsoft.WSMan.Management/ConfigProvider.cs @@ -43,12 +43,12 @@ public sealed class WSManConfigProvider : NavigationCmdletProvider, ICmdletProvi /// /// Object contains the cache of the enumerate results for the cmdlet to execute. /// - private Dictionary enumerateMapping = new Dictionary(); + private readonly Dictionary enumerateMapping = new Dictionary(); /// /// Mapping of ResourceURI with the XML returned by the Get call. /// - private Dictionary getMapping = new Dictionary(); + private readonly Dictionary getMapping = new Dictionary(); #region ICmdletProviderSupportsHelp Members @@ -62,7 +62,7 @@ public sealed class WSManConfigProvider : NavigationCmdletProvider, ICmdletProvi string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path) { // Get the leaf node from the path for which help is requested. - int ChildIndex = path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); + int ChildIndex = path.LastIndexOf('\\'); if (ChildIndex == -1) { // Means we are at host level, where no new-item is supported. Return empty string. @@ -193,7 +193,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) return null; } - if (string.IsNullOrEmpty(drive.Root) == false) + if (!string.IsNullOrEmpty(drive.Root)) { AssertError(helper.GetResourceMsgFromResourcetext("NewDriveRootDoesNotExist"), false); return null; @@ -439,7 +439,7 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether it has Chi // 3. Plugin and its internal structure Checks else if (WsManURI.Contains(WSManStringLiterals.containerPlugin)) { - strPathCheck = strPathCheck + WSManStringLiterals.containerPlugin; + strPathCheck += WSManStringLiterals.containerPlugin; // Check for Plugin path XmlDocument xmlPlugins = FindResourceValue(sessionobj, WsManURI, null); string currentpluginname = string.Empty; @@ -477,7 +477,7 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether it has Chi ArrayList arrSecurities = null; ArrayList arrResources = ProcessPluginResourceLevel(CurrentPluginXML, out arrSecurities); ArrayList arrInitParams = ProcessPluginInitParamLevel(CurrentPluginXML); - strPathCheck = strPathCheck + WSManStringLiterals.DefaultPathSeparator; + strPathCheck += WSManStringLiterals.DefaultPathSeparator; if (path.EndsWith(strPathCheck + WSManStringLiterals.containerResources, StringComparison.OrdinalIgnoreCase)) { if (arrResources != null && arrResources.Count > 0) @@ -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)) @@ -856,7 +859,7 @@ protected override void GetItem(string path) } } } - catch (PSArgumentNullException) { return;/*Leaving this known exception for no value found. Not Throwing error.*/} + catch (PSArgumentNullException) { return; /*Leaving this known exception for no value found. Not Throwing error.*/} catch (NullReferenceException) { return; /*Leaving this known exception for no value found. Not Throwing error.*/} } } @@ -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, - this.GetStringFromSecureString(runAsCredentials.Password)); - } + pluginConfiguration.UpdateOneConfiguration( + ".", + WSManStringLiterals.ConfigRunAsPasswordName, + GetStringFromSecureString(runAsCredentials.Password)); } if (WSManStringLiterals.ConfigRunAsPasswordName.Equals(ChildName, StringComparison.OrdinalIgnoreCase)) @@ -1050,7 +1048,7 @@ protected override void SetItem(string path, object value) AssertError(helper.GetResourceMsgFromResourcetext("SetItemOnRunAsPasswordNoRunAsUser"), false); } - value = this.GetStringFromSecureString(value); + value = GetStringFromSecureString(value); } pluginConfiguration.UpdateOneConfiguration(".", ChildName, value.ToString()); @@ -1237,7 +1235,7 @@ protected override void SetItem(string path, object value) pluginConfiguration.PutConfigurationOnServer(resourceUri); // Show Win RM service restart warning only when the changed setting is not picked up dynamically - if (settingPickedUpDynamically == false) + if (!settingPickedUpDynamically) { if (IsPathLocalMachine(host)) { @@ -1284,7 +1282,7 @@ protected override void SetItem(string path, object value) { if (!Force) { - string query = ""; + string query = string.Empty; string caption = helper.GetResourceMsgFromResourcetext("SetItemGeneralSecurityCaption"); if (ChildName.Equals("TrustedHosts", 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) { @@ -1473,7 +1470,7 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether Item is Co strPathCheck = host + WSManStringLiterals.DefaultPathSeparator; if (WsManURI.Contains(WSManStringLiterals.containerListener)) { - strPathCheck = strPathCheck + WSManStringLiterals.containerListener; + strPathCheck += WSManStringLiterals.containerListener; if (path.EndsWith(strPathCheck, StringComparison.OrdinalIgnoreCase)) { return true; @@ -1494,7 +1491,7 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether Item is Co // 2. Client Certificate Checks else if (WsManURI.Contains(WSManStringLiterals.containerCertMapping)) { - strPathCheck = strPathCheck + WSManStringLiterals.containerClientCertificate; + strPathCheck += WSManStringLiterals.containerClientCertificate; if (path.EndsWith(strPathCheck, StringComparison.OrdinalIgnoreCase)) { return true; @@ -1514,19 +1511,19 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether Item is Co // 3. Plugin and its internal structure Checks else if (WsManURI.Contains(WSManStringLiterals.containerPlugin)) { - strPathCheck = strPathCheck + WSManStringLiterals.containerPlugin; + strPathCheck += WSManStringLiterals.containerPlugin; // Check for Plugin path if (path.EndsWith(strPathCheck, StringComparison.OrdinalIgnoreCase)) { return true; } - strPathCheck = strPathCheck + WSManStringLiterals.DefaultPathSeparator; + strPathCheck += WSManStringLiterals.DefaultPathSeparator; XmlDocument xmlPlugins = FindResourceValue(sessionobj, WsManURI, null); string currentpluginname = string.Empty; GetPluginNames(xmlPlugins, out objPluginNames, out currentpluginname, path); - strPathCheck = strPathCheck + currentpluginname; + strPathCheck += currentpluginname; if (path.EndsWith(currentpluginname, StringComparison.OrdinalIgnoreCase)) { return true; @@ -1541,7 +1538,7 @@ WsMan Config Can be divided in to Four Fixed Regions to Check Whether Item is Co return true; } - strPathCheck = strPathCheck + WSManStringLiterals.DefaultPathSeparator; + strPathCheck += WSManStringLiterals.DefaultPathSeparator; if (path.EndsWith(strPathCheck + WSManStringLiterals.containerResources, StringComparison.OrdinalIgnoreCase)) { return true; @@ -1695,7 +1692,7 @@ protected override void RemoveItem(string path, bool recurse) return; } - strPathCheck = strPathCheck + WSManStringLiterals.containerPlugin; + strPathCheck += WSManStringLiterals.containerPlugin; int pos = 0; string pName = null; pos = path.LastIndexOf(strPathCheck + WSManStringLiterals.DefaultPathSeparator, StringComparison.OrdinalIgnoreCase) + strPathCheck.Length + 1; @@ -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)) { @@ -2152,11 +2147,10 @@ private void NewItemPluginOrPluginChild(object sessionobj, string path, string h strPathChk = strPathChk + WSManStringLiterals.DefaultPathSeparator + pName + WSManStringLiterals.DefaultPathSeparator; if (path.Contains(strPathChk + WSManStringLiterals.containerResources)) { - strPathChk = strPathChk + WSManStringLiterals.containerResources; + 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)); @@ -2290,7 +2284,7 @@ private void NewItemPluginOrPluginChild(object sessionobj, string path, string h /// /// /// - private PSObject GetItemPSObjectWithTypeName(string Name, string TypeNameOfElement, object Value, string[] keys, string ExtendedTypeName, WsManElementObjectTypes WSManElementObjectType, PSObject input = null) + private static PSObject GetItemPSObjectWithTypeName(string Name, string TypeNameOfElement, object Value, string[] keys, string ExtendedTypeName, WsManElementObjectTypes WSManElementObjectType, PSObject input = null) { PSObject mshObject = null; if (WSManElementObjectType.Equals(WsManElementObjectTypes.WSManConfigElement)) @@ -2328,7 +2322,7 @@ private PSObject GetItemPSObjectWithTypeName(string Name, string TypeNameOfEleme if (mshObject != null) { types.Append(mshObject.ImmediateBaseObject.GetType().FullName); - types.Append("#"); + types.Append('#'); types.Append(ExtendedTypeName); mshObject.TypeNames.Insert(0, types.ToString()); } @@ -2410,7 +2404,7 @@ private void SetItemListenerOrClientCertificate(object sessionObj, string Resour /// /// /// - private string GetInputStringForCreate(string ResourceURI, Hashtable value, string host) + private static string GetInputStringForCreate(string ResourceURI, Hashtable value, string host) { string putstr = string.Empty; string nilns = string.Empty; @@ -2426,16 +2420,16 @@ private string GetInputStringForCreate(string ResourceURI, Hashtable value, stri sbvalues.Append(key); if (value[key] == null) { - sbvalues.Append(" "); + sbvalues.Append(' '); sbvalues.Append(WSManStringLiterals.ATTR_NIL); nilns = " " + WSManStringLiterals.NS_XSI; } - sbvalues.Append(">"); + sbvalues.Append('>'); sbvalues.Append(EscapeValuesForXML(((Hashtable)value)[key].ToString())); sbvalues.Append(""); + sbvalues.Append('>'); } } } @@ -2523,7 +2517,7 @@ private string GetHostName(string path) return sHostname; } - private string GetRootNodeName(string ResourceURI) + private static string GetRootNodeName(string ResourceURI) { string tempuri = string.Empty; if (ResourceURI.Contains('?')) @@ -2531,7 +2525,7 @@ private string GetRootNodeName(string ResourceURI) ResourceURI = ResourceURI.Split('?').GetValue(0).ToString(); } - string PTRN_URI_LAST = "([a-z_][-a-z0-9._]*)$"; + const string PTRN_URI_LAST = "([a-z_][-a-z0-9._]*)$"; Regex objregex = new Regex(PTRN_URI_LAST, RegexOptions.IgnoreCase); MatchCollection regexmatch = objregex.Matches(ResourceURI); if (regexmatch.Count > 0) @@ -2542,7 +2536,7 @@ private string GetRootNodeName(string ResourceURI) return tempuri; } - private string EscapeValuesForXML(string value) + private static string EscapeValuesForXML(string value) { StringBuilder esc_str = new StringBuilder(); for (int i = 0; i <= value.Length - 1; i++) @@ -2585,7 +2579,7 @@ private string EscapeValuesForXML(string value) return esc_str.ToString(); } - private bool IsItemContainer(XmlNodeList nodes) + private static bool IsItemContainer(XmlNodeList nodes) { bool result = false; if (nodes.Count != 0) @@ -2768,7 +2762,7 @@ private XmlDocument EnumerateResourceValue(object sessionobj, string ResourceURI while (!((IWSManEnumerator)value).AtEndOfStream) { - strXmlValue = strXmlValue + ((IWSManEnumerator)value).ReadItem(); + strXmlValue += ((IWSManEnumerator)value).ReadItem(); } Marshal.ReleaseComObject(value); @@ -2900,7 +2894,7 @@ private bool ContainResourceValue(object sessionobj, string ResourceURI, string while (!((IWSManEnumerator)value).AtEndOfStream) { - valuexml = valuexml + ((IWSManEnumerator)value).ReadItem(); + valuexml += ((IWSManEnumerator)value).ReadItem(); } if ((valuexml != string.Empty) && !(string.IsNullOrEmpty(valuexml))) @@ -2935,24 +2929,24 @@ private bool ContainResourceValue(object sessionobj, string ResourceURI, string #endregion "WsMan linking Operations" - private string GetURIWithFilter(string uri, Hashtable cmdlinevalues) + private static string GetURIWithFilter(string uri, Hashtable cmdlinevalues) { StringBuilder sburi = new StringBuilder(uri); if (cmdlinevalues != null) { if (uri.Contains("Config/Listener")) { - sburi.Append("?"); + sburi.Append('?'); sburi.Append(GetFilterString(cmdlinevalues, PKeyListener)); } else if (uri.Contains("Config/Service/certmapping")) { - sburi.Append("?"); + sburi.Append('?'); sburi.Append(GetFilterString(cmdlinevalues, PKeyCertMapping)); } else if (uri.Contains("Config/Plugin")) { - sburi.Append("?"); + sburi.Append('?'); sburi.Append(GetFilterString(cmdlinevalues, PKeyPlugin)); } } @@ -2960,7 +2954,7 @@ private string GetURIWithFilter(string uri, Hashtable cmdlinevalues) return sburi.ToString(); } - private string GetFilterString(Hashtable cmdlinevalues, string[] pkey) + private static string GetFilterString(Hashtable cmdlinevalues, string[] pkey) { StringBuilder filter = new StringBuilder(); foreach (string key in pkey) @@ -2968,9 +2962,9 @@ private string GetFilterString(Hashtable cmdlinevalues, string[] pkey) if (cmdlinevalues.Contains(key)) { filter.Append(key); - filter.Append("="); + filter.Append('='); filter.Append(cmdlinevalues[key].ToString()); - filter.Append("+"); + filter.Append('+'); } } @@ -2979,7 +2973,7 @@ private string GetFilterString(Hashtable cmdlinevalues, string[] pkey) return filter.ToString(); } - private bool IsPKey(string value, string ResourceURI) + private static bool IsPKey(string value, string ResourceURI) { bool result = false; if (ResourceURI.Contains(WSManStringLiterals.containerListener)) @@ -2998,7 +2992,7 @@ private bool IsPKey(string value, string ResourceURI) return result; } - private bool CheckPkeysArray(Hashtable values, string value, string[] pkeys) + private static bool CheckPkeysArray(Hashtable values, string value, string[] pkeys) { bool result = false; if (values != null) @@ -3102,7 +3096,7 @@ private void WritePSObjectPropertiesAsWSManElementObjects(PSObject psobject, str if (mshObject != null) { types.Append(mshObject.ImmediateBaseObject.GetType().FullName); - types.Append("#"); + types.Append('#'); types.Append(ExtendedTypeName); mshObject.TypeNames.Insert(0, types.ToString()); } @@ -3239,7 +3233,7 @@ private PSObject BuildHostLevelPSObjectArrayList(object objSessionObject, string /// /// /// - private PSObject ConvertToPSObject(XmlNode xmlnode) + private static PSObject ConvertToPSObject(XmlNode xmlnode) { PSObject mshObject = new PSObject(); foreach (XmlNode node in xmlnode.ChildNodes) @@ -3275,7 +3269,7 @@ private PSObject ConvertToPSObject(XmlNode xmlnode) return mshObject; } - private string SetXPathString(string uri) + private static string SetXPathString(string uri) { string parent = uri.Substring(uri.LastIndexOf(WSManStringLiterals.WinrmPathSeparator.ToString(), StringComparison.OrdinalIgnoreCase) + 1); if (parent.Equals(WSManStringLiterals.containerWinrs, StringComparison.OrdinalIgnoreCase)) @@ -3307,7 +3301,7 @@ private string SetXPathString(string uri) return parent; } - private string SetSchemaPath(string uri) + private static string SetSchemaPath(string uri) { string schemapath = string.Empty; uri = uri.Remove(0, WinrmRootName[0].Length); @@ -3334,7 +3328,7 @@ private string SetSchemaPath(string uri) /// /// /// - private string NormalizePath(string path, string host) + private static string NormalizePath(string path, string host) { string uri = string.Empty; if (path.StartsWith(host, StringComparison.OrdinalIgnoreCase)) @@ -3413,10 +3407,7 @@ private 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)) @@ -3573,7 +3564,7 @@ private string GetCorrectCaseOfName(string ChildName, string hostname, string pa string result = ChildName; if (ChildName != null) { - if (!ChildName.Contains("_")) + if (!ChildName.Contains('_')) { if (ChildName.Equals(WSManStringLiterals.containerQuotasParameters, StringComparison.OrdinalIgnoreCase)) result = WSManStringLiterals.containerQuotasParameters; @@ -3647,20 +3638,20 @@ private string GetCorrectCaseOfName(string ChildName, string hostname, string pa else { if (ChildName.StartsWith(WSManStringLiterals.containerListener, StringComparison.OrdinalIgnoreCase)) - result = WSManStringLiterals.containerListener + "_" + ChildName.Substring(ChildName.IndexOf('_') + 1); + result = string.Concat(WSManStringLiterals.containerListener, "_", ChildName.AsSpan(ChildName.IndexOf('_') + 1)); if (ChildName.StartsWith(WSManStringLiterals.containerSingleResource, StringComparison.OrdinalIgnoreCase)) - result = WSManStringLiterals.containerSingleResource + "_" + ChildName.Substring(ChildName.IndexOf('_') + 1); + result = string.Concat(WSManStringLiterals.containerSingleResource, "_", ChildName.AsSpan(ChildName.IndexOf('_') + 1)); if (ChildName.StartsWith(WSManStringLiterals.containerSecurity, StringComparison.OrdinalIgnoreCase)) - result = WSManStringLiterals.containerSecurity + "_" + ChildName.Substring(ChildName.IndexOf('_') + 1); + result = string.Concat(WSManStringLiterals.containerSecurity, "_", ChildName.AsSpan(ChildName.IndexOf('_') + 1)); if (ChildName.StartsWith(WSManStringLiterals.containerClientCertificate, StringComparison.OrdinalIgnoreCase)) - result = WSManStringLiterals.containerClientCertificate + "_" + ChildName.Substring(ChildName.IndexOf('_') + 1); + result = string.Concat(WSManStringLiterals.containerClientCertificate, "_", ChildName.AsSpan(ChildName.IndexOf('_') + 1)); } } return result; } - private ArrayList RemoveItemfromResourceArray(ArrayList resourceArray, string ChildName, string type, string property) + private static ArrayList RemoveItemfromResourceArray(ArrayList resourceArray, string ChildName, string type, string property) { if (resourceArray != null) { @@ -4089,7 +4080,7 @@ private bool ItemExistListenerOrClientCertificate(object sessionobj, string Reso CurrentNode = RemainingPath.Substring(0, pos); } - if (objcache.Contains(CurrentNode) == false) + if (!objcache.Contains(CurrentNode)) { return false; } @@ -4519,7 +4510,7 @@ private void GetChildItemsOrNames(string path, ProviderMethods methodname, bool /// /// /// - private int GetPluginNames(XmlDocument xmlPlugins, out PSObject PluginNames, out string CurrentPluginName, string path) + private static int GetPluginNames(XmlDocument xmlPlugins, out PSObject PluginNames, out string CurrentPluginName, string path) { PluginNames = new PSObject(); CurrentPluginName = string.Empty; @@ -4627,7 +4618,7 @@ private void StartWSManService(bool force) /// /// /// - private bool IsPathLocalMachine(string host) + private static bool IsPathLocalMachine(string host) { bool hostfound = false; // Check is Localhost @@ -4684,7 +4675,7 @@ private bool IsPathLocalMachine(string host) #region Plugin private functions - private void GenerateObjectNameAndKeys(Hashtable InputAttributes, string ResourceURI, string ContainerItem, out string ItemName, out string[] keys) + private static void GenerateObjectNameAndKeys(Hashtable InputAttributes, string ResourceURI, string ContainerItem, out string ItemName, out string[] keys) { StringBuilder sbHashKey = new StringBuilder(); string keysColumns = string.Empty; @@ -4717,7 +4708,7 @@ private void GenerateObjectNameAndKeys(Hashtable InputAttributes, string Resourc keys = keysColumns.Split('|'); } - private void ProcessCertMappingObjects(XmlDocument xmlCerts, out Hashtable Certcache, out Hashtable Keyscache) + private static void ProcessCertMappingObjects(XmlDocument xmlCerts, out Hashtable Certcache, out Hashtable Keyscache) { Hashtable lCache = new Hashtable(); Hashtable kCache = new Hashtable(); @@ -4775,7 +4766,7 @@ private void ProcessCertMappingObjects(XmlDocument xmlCerts, out Hashtable Certc Keyscache = kCache; } - private void ProcessListenerObjects(XmlDocument xmlListeners, out Hashtable listenercache, out Hashtable Keyscache) + private static void ProcessListenerObjects(XmlDocument xmlListeners, out Hashtable listenercache, out Hashtable Keyscache) { Hashtable lCache = new Hashtable(); Hashtable kCache = new Hashtable(); @@ -4836,7 +4827,7 @@ private void ProcessListenerObjects(XmlDocument xmlListeners, out Hashtable list Keyscache = kCache; } - private PSObject ProcessPluginConfigurationLevel(XmlDocument xmldoc, bool setRunasPasswordAsSecureString = false) + private static PSObject ProcessPluginConfigurationLevel(XmlDocument xmldoc, bool setRunasPasswordAsSecureString = false) { PSObject objConfiglvl = null; @@ -4877,7 +4868,7 @@ private PSObject ProcessPluginConfigurationLevel(XmlDocument xmldoc, bool setRun return objConfiglvl; } - private ArrayList ProcessPluginResourceLevel(XmlDocument xmldoc, out ArrayList arrSecurity) + private static ArrayList ProcessPluginResourceLevel(XmlDocument xmldoc, out ArrayList arrSecurity) { ArrayList Resources = null; ArrayList nSecurity = null; @@ -4963,7 +4954,7 @@ private ArrayList ProcessPluginResourceLevel(XmlDocument xmldoc, out ArrayList a return Resources; } - private ArrayList ProcessPluginInitParamLevel(XmlDocument xmldoc) + private static ArrayList ProcessPluginInitParamLevel(XmlDocument xmldoc) { ArrayList InitParamLvl = null; if (xmldoc != null) @@ -5001,7 +4992,7 @@ private ArrayList ProcessPluginInitParamLevel(XmlDocument xmldoc) return InitParamLvl; } - private ArrayList ProcessPluginSecurityLevel(ArrayList arrSecurity, XmlDocument xmlSecurity, string UniqueResourceID, string ParentResourceUri) + private static ArrayList ProcessPluginSecurityLevel(ArrayList arrSecurity, XmlDocument xmlSecurity, string UniqueResourceID, string ParentResourceUri) { // ArrayList SecurityLvl = null; if (xmlSecurity != null) @@ -5059,11 +5050,11 @@ private ArrayList ProcessPluginSecurityLevel(ArrayList arrSecurity, XmlDocument /// 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 string ConstructPluginXml(PSObject objinputparam, string ResourceURI, string host, string Operation, ArrayList resources, ArrayList securities, ArrayList initParams) + private static string ConstructPluginXml(PSObject objinputparam, string ResourceURI, string host, string Operation, ArrayList resources, ArrayList securities, ArrayList initParams) { StringBuilder sbvalues = new StringBuilder(); sbvalues.Append(" /// Value to append. - private string GetStringFromSecureString(object propertyValue) + 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); @@ -5206,7 +5196,7 @@ private string GetStringFromSecureString(object propertyValue) return passwordValueToAdd; } - private string ConstructResourceXml(PSObject objinputparams, ArrayList resources, ArrayList securities) + private static string ConstructResourceXml(PSObject objinputparams, ArrayList resources, ArrayList securities) { StringBuilder sbvalues = new StringBuilder(string.Empty); if (objinputparams == null && resources == null) @@ -5293,7 +5283,7 @@ private string ConstructResourceXml(PSObject objinputparams, ArrayList resources return sbvalues.ToString(); } - private string ConstructSecurityXml(PSObject objinputparams, ArrayList securities, string strResourceIdentity) + private static string ConstructSecurityXml(PSObject objinputparams, ArrayList securities, string strResourceIdentity) { // StringBuilder sbvalues = new StringBuilder(string.Empty); @@ -5320,7 +5310,7 @@ private string ConstructSecurityXml(PSObject objinputparams, ArrayList securitie return sbvalues.ToString(); } - private void AddSecurityProperties( + private static void AddSecurityProperties( PSMemberInfoCollection properties, StringBuilder sbValues) { @@ -5345,7 +5335,7 @@ private void AddSecurityProperties( sbValues.Append(""); } - private string ConstructInitParamsXml(PSObject objinputparams, ArrayList initparams) + private static string ConstructInitParamsXml(PSObject objinputparams, ArrayList initparams) { // // @@ -5397,7 +5387,7 @@ private string ConstructInitParamsXml(PSObject objinputparams, ArrayList initpar return sbvalues.ToString(); } - private string ConstructCapabilityXml(object[] capabilities) + private static string ConstructCapabilityXml(object[] capabilities) { StringBuilder sbvalues = new StringBuilder(string.Empty); foreach (object cap in capabilities) @@ -5413,7 +5403,7 @@ private string ConstructCapabilityXml(object[] capabilities) return sbvalues.ToString(); } - private bool IsValueOfParamList(string name, string[] paramcontainer) + private static bool IsValueOfParamList(string name, string[] paramcontainer) { bool result = false; foreach (string value in paramcontainer) @@ -5434,14 +5424,14 @@ private enum ProviderMethods { GetChildItems, GetChildNames - }; + } private enum WsManElementObjectTypes { WSManConfigElement, WSManConfigContainerElement, WSManConfigLeafElement - }; + } #region def private static readonly string[] WinrmRootName = new string[] { "winrm/Config" }; @@ -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; } @@ -6585,4 +6575,3 @@ public string[] Keys #endregion "WsMan Output Objects" } - diff --git a/src/Microsoft.WSMan.Management/CredSSP.cs b/src/Microsoft.WSMan.Management/CredSSP.cs index 52c4f4e921d..a8457ab5bc2 100644 --- a/src/Microsoft.WSMan.Management/CredSSP.cs +++ b/src/Microsoft.WSMan.Management/CredSSP.cs @@ -97,7 +97,6 @@ internal IWSManSession CreateWSManSession() /// the server, hence allowing the user to perform management operations that /// access a second hop. /// - [Cmdlet(VerbsLifecycle.Disable, "WSManCredSSP", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096628")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Cred")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SSP")] @@ -122,16 +121,11 @@ private void DisableClientSideSettings() { string result = m_SessionObj.Get(helper.CredSSP_RUri, 0); XmlDocument resultopxml = new XmlDocument(); - string inputXml = null; resultopxml.LoadXml(result); XmlNamespaceManager nsmgr = new XmlNamespaceManager(resultopxml.NameTable); nsmgr.AddNamespace("cfg", helper.CredSSP_XMLNmsp); XmlNode xNode = resultopxml.SelectSingleNode(helper.CredSSP_SNode, nsmgr); - if (!(xNode == null)) - { - inputXml = @"false"; - } - else + if (xNode is null) { InvalidOperationException ex = new InvalidOperationException(); ErrorRecord er = new ErrorRecord(ex, helper.GetResourceMsgFromResourcetext("WinrmNotConfigured"), ErrorCategory.InvalidOperation, null); @@ -139,6 +133,8 @@ private void DisableClientSideSettings() return; } + string inputXml = @"false"; + m_SessionObj.Put(helper.CredSSP_RUri, inputXml, 0); if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) @@ -189,19 +185,12 @@ private void DisableServerSideSettings() { string result = m_SessionObj.Get(helper.Service_CredSSP_Uri, 0); XmlDocument resultopxml = new XmlDocument(); - string inputXml = null; resultopxml.LoadXml(result); XmlNamespaceManager nsmgr = new XmlNamespaceManager(resultopxml.NameTable); nsmgr.AddNamespace("cfg", helper.Service_CredSSP_XMLNmsp); XmlNode xNode = resultopxml.SelectSingleNode(helper.CredSSP_SNode, nsmgr); - if (!(xNode == null)) - { - inputXml = string.Format(CultureInfo.InvariantCulture, - @"false", - helper.Service_CredSSP_XMLNmsp); - } - else + if (xNode is null) { InvalidOperationException ex = new InvalidOperationException(); ErrorRecord er = new ErrorRecord(ex, @@ -211,6 +200,11 @@ private void DisableServerSideSettings() return; } + string inputXml = string.Format( + CultureInfo.InvariantCulture, + @"false", + helper.Service_CredSSP_XMLNmsp); + m_SessionObj.Put(helper.Service_CredSSP_Uri, inputXml, 0); } finally @@ -234,7 +228,7 @@ private void DeleteUserDelegateSettings() GPO.OpenLocalMachineGPO(1); KeyHandle = GPO.GetRegistryKey(2); RegistryKey rootKey = Registry.CurrentUser; - string GPOpath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy Objects"; + const string GPOpath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy Objects"; RegistryKey GPOKey = rootKey.OpenSubKey(GPOpath, true); foreach (string keyname in GPOKey.GetSubKeyNames()) { @@ -396,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*/ @@ -418,7 +413,7 @@ public string[] DelegateComputer /// /// Property that sets force parameter. /// - [Parameter()] + [Parameter] public SwitchParameter Force { get { return force; } @@ -515,10 +510,11 @@ private void EnableClientSideSettings() return; } - string newxmlcontent = @"true"; + const string newxmlcontent = @"true"; try { XmlDocument xmldoc = new XmlDocument(); + // push the xml string with credssp enabled xmldoc.LoadXml(m_SessionObj.Put(helper.CredSSP_RUri, newxmlcontent, 0)); @@ -598,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); @@ -633,7 +631,7 @@ private void UpdateCurrentUserRegistrySettings() GPO.OpenLocalMachineGPO(1); KeyHandle = GPO.GetRegistryKey(2); RegistryKey rootKey = Registry.CurrentUser; - string GPOpath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy Objects"; + const string GPOpath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy Objects"; RegistryKey GPOKey = rootKey.OpenSubKey(GPOpath, true); foreach (string keyname in GPOKey.GetSubKeyNames()) { @@ -743,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 @@ -834,7 +833,7 @@ protected override void BeginProcessing() } // The application name MUST be "wsman" as wsman got approval from security // folks who suggested to register the SPN with name "wsman". - string applicationname = "wsman"; + const string applicationname = "wsman"; string credsspResult = GetDelegateSettings(applicationname); if (string.IsNullOrEmpty(credsspResult)) { diff --git a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs index 01ba71f68a7..3182c04c535 100644 --- a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs +++ b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs @@ -11,7 +11,7 @@ namespace Microsoft.WSMan.Management /// Class that queries the server and gets current configurations. /// Also provides a generic way to update the configurations. /// - internal class CurrentConfigurations + internal sealed class CurrentConfigurations { /// /// Prefix used to add NameSpace of root element to namespace manager. @@ -21,7 +21,7 @@ internal class CurrentConfigurations /// /// This holds the current configurations XML. /// - private XmlDocument rootDocument; + private readonly XmlDocument rootDocument; /// /// Holds the reference to the current document element. @@ -36,7 +36,7 @@ internal class CurrentConfigurations /// /// Session of the WsMan sserver. /// - private IWSManSession serverSession; + private readonly IWSManSession serverSession; /// /// Gets the server session associated with the configuration. @@ -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( @@ -131,7 +119,7 @@ public void RemoveOneConfiguration(string pathToNodeFromRoot) { if (nodeToRemove is XmlAttribute) { - this.RemoveAttribute(nodeToRemove as XmlAttribute); + RemoveAttribute(nodeToRemove as XmlAttribute); } } else @@ -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( @@ -217,7 +191,7 @@ public string GetOneConfiguration(string pathFromRoot) /// Removes the attribute from OwnerNode. /// /// Attribute to Remove. - private void RemoveAttribute(XmlAttribute attributeToRemove) + private static void RemoveAttribute(XmlAttribute attributeToRemove) { XmlElement ownerElement = attributeToRemove.OwnerElement; ownerElement.RemoveAttribute(attributeToRemove.Name); diff --git a/src/Microsoft.WSMan.Management/Interop.cs b/src/Microsoft.WSMan.Management/Interop.cs index 7df5d16fc93..3abb938a572 100644 --- a/src/Microsoft.WSMan.Management/Interop.cs +++ b/src/Microsoft.WSMan.Management/Interop.cs @@ -23,7 +23,6 @@ namespace Microsoft.WSMan.Management #region WsManEnumFlags /// _WSManEnumFlags enumeration. - [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] [TypeLibType((short)0)] public enum WSManEnumFlags @@ -174,7 +173,7 @@ public enum AuthenticationMechanism [ComImport] [TypeLibType((short)4304)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -256,7 +255,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -313,7 +312,7 @@ string Password [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -337,7 +336,7 @@ string CertificateThumbprint [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -377,7 +376,7 @@ void SetProxy(int accessType, /// ProxyAuthenticationUseDigest method of IWSManConnectionOptionsEx2 interface. [DispId(11)] int ProxyAuthenticationUseDigest(); - }; + } #endregion IWSManConnectionOptions @@ -387,7 +386,7 @@ void SetProxy(int accessType, [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -451,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 @@ -707,7 +706,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -736,13 +735,13 @@ public interface IWSManResourceLocator string ResourceUri { // IDL: HRESULT resourceUri (BSTR value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] set; // IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue); - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] @@ -820,14 +819,16 @@ string FragmentDialect int MustUnderstandOptions { - // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); - - [DispId(7)] - get; // IDL: HRESULT MustUnderstandOptions (long value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [DispId(7)] set; + + // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); + + [DispId(7)] + get; } /// ClearOptions method of IWSManResourceLocator interface. Clear all options @@ -862,7 +863,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -926,7 +927,6 @@ public interface IWSManSession /// /// /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] [DispId(5)] @@ -1008,7 +1008,7 @@ int Timeout [ComImport] [TypeLibType((short)400)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif diff --git a/src/Microsoft.WSMan.Management/InvokeWSManAction.cs b/src/Microsoft.WSMan.Management/InvokeWSManAction.cs index 71f8ae4c84d..da92a680ab3 100644 --- a/src/Microsoft.WSMan.Management/InvokeWSManAction.cs +++ b/src/Microsoft.WSMan.Management/InvokeWSManAction.cs @@ -23,8 +23,8 @@ namespace Microsoft.WSMan.Management /// Invoke-WSManAction -Action StartService -ResourceURI wmicimv2/Win32_Service /// -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 { /// @@ -69,7 +69,10 @@ public string ApplicationName [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -144,20 +147,20 @@ 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". /// SelectorSet is a hash table which helps in identify an instance of the - /// management resource if there are are more than 1 instance of the resource + /// management resource if there are more than 1 instance of the resource /// class. /// [Parameter(Position = 2, @@ -245,7 +248,7 @@ public Uri ResourceURI private Uri resourceuri; private WSManHelper helper; - private IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); + private readonly IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); private IWSManSession m_session = null; private string connectionStr = string.Empty; diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 9e7a0dfd414..d7b6d1235da 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -2,6 +2,7 @@ PowerShell's Microsoft.WSMan.Management project + $(NoWarn);CA1416 Microsoft.WSMan.Management @@ -9,7 +10,7 @@ - + diff --git a/src/Microsoft.WSMan.Management/NewWSManSession.cs b/src/Microsoft.WSMan.Management/NewWSManSession.cs index b2d6719e7e9..09b22af9924 100644 --- a/src/Microsoft.WSMan.Management/NewWSManSession.cs +++ b/src/Microsoft.WSMan.Management/NewWSManSession.cs @@ -25,8 +25,8 @@ namespace Microsoft.WSMan.Management /// Invoke-WSManAction /// Connect-WSMan. /// - [Cmdlet(VerbsCommon.New, "WSManSessionOption", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096845")] + [OutputType(typeof(SessionOption))] public class NewWSManSessionOptionCommand : PSCmdlet { /// @@ -61,7 +61,10 @@ public ProxyAccessType ProxyAccessType [ValidateNotNullOrEmpty] public ProxyAuthentication ProxyAuthentication { - get { return proxyauthentication; } + get + { + return proxyauthentication; + } set { @@ -79,7 +82,10 @@ public ProxyAuthentication ProxyAuthentication [Credential] public PSCredential ProxyCredential { - get { return _proxycredential; } + get + { + return _proxycredential; + } set { @@ -100,7 +106,10 @@ public PSCredential ProxyCredential [Parameter] public SwitchParameter SkipCACheck { - get { return skipcacheck; } + get + { + return skipcacheck; + } set { @@ -119,7 +128,10 @@ public SwitchParameter SkipCACheck [Parameter] public SwitchParameter SkipCNCheck { - get { return skipcncheck; } + get + { + return skipcncheck; + } set { @@ -138,7 +150,10 @@ public SwitchParameter SkipCNCheck [Parameter] public SwitchParameter SkipRevocationCheck { - get { return skiprevocationcheck; } + get + { + return skiprevocationcheck; + } set { @@ -156,10 +171,13 @@ 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 { return spnport; } + get + { + return spnport; + } set { @@ -167,7 +185,7 @@ public Int32 SPNPort } } - private Int32 spnport; + private int spnport; /// /// The following is the definition of the input parameter "Timeout". @@ -175,10 +193,13 @@ public Int32 SPNPort /// [Parameter] [Alias("OperationTimeoutMSec")] - [ValidateRange(0, Int32.MaxValue)] - public Int32 OperationTimeout + [ValidateRange(0, int.MaxValue)] + public int OperationTimeout { - get { return operationtimeout; } + get + { + return operationtimeout; + } set { @@ -186,7 +207,7 @@ public Int32 OperationTimeout } } - private Int32 operationtimeout; + private int operationtimeout; /// /// The following is the definition of the input parameter "UnEncrypted". @@ -197,7 +218,10 @@ public Int32 OperationTimeout [Parameter] public SwitchParameter NoEncryption { - get { return noencryption; } + get + { + return noencryption; + } set { @@ -216,7 +240,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/Microsoft.WSMan.Management/PingWSMan.cs b/src/Microsoft.WSMan.Management/PingWSMan.cs index 9581eaabc33..88b443a6ef0 100644 --- a/src/Microsoft.WSMan.Management/PingWSMan.cs +++ b/src/Microsoft.WSMan.Management/PingWSMan.cs @@ -22,8 +22,8 @@ namespace Microsoft.WSMan.Management /// Issues an operation against the remote machine to ensure that the wsman /// service is running. /// - [Cmdlet(VerbsDiagnostic.Test, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2097114")] + [OutputType(typeof(XmlElement))] public class TestWSManCommand : AuthenticatingWSManCommand, IDisposable { /// @@ -36,7 +36,10 @@ public class TestWSManCommand : AuthenticatingWSManCommand, IDisposable [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -73,7 +76,10 @@ public string ComputerName [Alias("auth", "am")] public override AuthenticationMechanism Authentication { - get { return authentication; } + get + { + return authentication; + } set { @@ -90,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 4aeb283b81f..cac5196740f 100644 --- a/src/Microsoft.WSMan.Management/WSManConnections.cs +++ b/src/Microsoft.WSMan.Management/WSManConnections.cs @@ -38,7 +38,10 @@ public class AuthenticatingWSManCommand : PSCmdlet [Alias("cred", "c")] public virtual PSCredential Credential { - get { return credential; } + get + { + return credential; + } set { @@ -69,7 +72,10 @@ public virtual PSCredential Credential [Alias("auth", "am")] public virtual AuthenticationMechanism Authentication { - get { return authentication; } + get + { + return authentication; + } set { @@ -88,7 +94,10 @@ public virtual AuthenticationMechanism Authentication [ValidateNotNullOrEmpty] public virtual string CertificateThumbprint { - get { return thumbPrint; } + get + { + return thumbPrint; + } set { @@ -144,7 +153,10 @@ public string ApplicationName [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -201,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". @@ -288,7 +300,6 @@ protected override void BeginProcessing() /// is the local computer. Type the fully qualified domain name, NETBIOS name or /// IP address to indicate the remote host(s) /// - [Cmdlet(VerbsCommunications.Disconnect, "WSMan", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=2096839")] public class DisconnectWSManCommand : PSCmdlet, IDisposable { @@ -301,7 +312,10 @@ public class DisconnectWSManCommand : PSCmdlet, IDisposable [Parameter(Position = 0)] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -346,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 5a3d6b74f7f..c96b002123d 100644 --- a/src/Microsoft.WSMan.Management/WSManInstance.cs +++ b/src/Microsoft.WSMan.Management/WSManInstance.cs @@ -28,8 +28,8 @@ namespace Microsoft.WSMan.Management /// Invoke-WSManAction -Action StartService -ResourceURI wmicimv2/Win32_Service /// -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 @@ -41,7 +41,10 @@ public class GetWSManInstanceCommand : AuthenticatingWSManCommand, IDisposable [Parameter(ParameterSetName = "Enumerate")] public string ApplicationName { - get { return applicationname; } + get + { + return applicationname; + } set { @@ -61,7 +64,10 @@ public string ApplicationName [Alias("UBPO", "Base")] public SwitchParameter BasePropertiesOnly { - get { return basepropertiesonly; } + get + { + return basepropertiesonly; + } set { @@ -82,7 +88,10 @@ public SwitchParameter BasePropertiesOnly [Alias("CN")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -102,7 +111,6 @@ public string ComputerName /// remote machine. The format of this string is: /// transport://server:port/Prefix. /// - [Parameter( ParameterSetName = "GetInstance")] [Parameter( @@ -111,7 +119,10 @@ public string ComputerName [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] public Uri ConnectionURI { - get { return connectionuri; } + get + { + return connectionuri; + } set { @@ -128,7 +139,10 @@ public Uri ConnectionURI [Parameter] public Uri Dialect { - get { return dialect; } + get + { + return dialect; + } set { @@ -143,12 +157,14 @@ public Uri Dialect /// Switch indicates list all instances of a management resource. Equivalent to /// WSManagement Enumerate. /// - [Parameter(Mandatory = true, ParameterSetName = "Enumerate")] public SwitchParameter Enumerate { - get { return enumerate; } + get + { + return enumerate; + } set { @@ -166,7 +182,10 @@ public SwitchParameter Enumerate [ValidateNotNullOrEmpty] public string Filter { - get { return filter; } + get + { + return filter; + } set { @@ -181,12 +200,14 @@ public string Filter /// Specifies a section inside the instance that is to be updated or retrieved /// for the given operation. /// - [Parameter(ParameterSetName = "GetInstance")] [ValidateNotNullOrEmpty] public string Fragment { - get { return fragment; } + get + { + return fragment; + } set { @@ -208,7 +229,10 @@ public string Fragment [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Hashtable OptionSet { - get { return optionset; } + get + { + return optionset; + } set { @@ -224,9 +248,12 @@ public Hashtable OptionSet /// [Parameter(ParameterSetName = "Enumerate")] [Parameter(ParameterSetName = "GetInstance")] - public Int32 Port + public int Port { - get { return port; } + get + { + return port; + } set { @@ -234,7 +261,7 @@ public Int32 Port } } - private Int32 port = 0; + private int port = 0; /// /// The following is the definition of the input parameter "Associations". @@ -242,11 +269,13 @@ public Int32 Port /// associated instances. This can only be used when specifying the Dialect as /// Association. /// - [Parameter(ParameterSetName = "Enumerate")] public SwitchParameter Associations { - get { return associations; } + get + { + return associations; + } set { @@ -269,7 +298,10 @@ public SwitchParameter Associations [Alias("RURI")] public Uri ResourceURI { - get { return resourceuri; } + get + { + return resourceuri; + } set { @@ -294,11 +326,14 @@ public Uri ResourceURI [Parameter(ParameterSetName = "Enumerate")] [ValidateNotNullOrEmpty] - [ValidateSetAttribute(new string[] { "object", "epr", "objectandepr" })] + [ValidateSet(new string[] { "object", "epr", "objectandepr" })] [Alias("RT")] public string ReturnType { - get { return returntype; } + get + { + return returntype; + } set { @@ -311,7 +346,7 @@ public string ReturnType /// /// The following is the definition of the input parameter "SelectorSet". /// SelectorSet is a hash table which helps in identify an instance of the - /// management resource if there are are more than 1 instance of the resource + /// management resource if there are more than 1 instance of the resource /// class. /// [Parameter( @@ -320,7 +355,10 @@ public string ReturnType [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public Hashtable SelectorSet { - get { return selectorset; } + get + { + return selectorset; + } set { @@ -341,7 +379,10 @@ public Hashtable SelectorSet [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public SessionOption SessionOption { - get { return sessionoption; } + get + { + return sessionoption; + } set { @@ -361,7 +402,10 @@ public SessionOption SessionOption public SwitchParameter Shallow { - get { return shallow; } + get + { + return shallow; + } set { @@ -384,7 +428,10 @@ public SwitchParameter Shallow [Alias("SSL")] public SwitchParameter UseSSL { - get { return usessl; } + get + { + return usessl; + } set { @@ -396,7 +443,7 @@ public SwitchParameter UseSSL #endregion parameter - # region private + #region private private WSManHelper helper; private string GetFilter() @@ -418,7 +465,7 @@ private string GetFilter() filter = filter + "" + value + ""; } - filter = filter + ""; + filter += ""; return (filter); } @@ -639,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 @@ -667,7 +715,10 @@ public string ApplicationName [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -754,7 +805,6 @@ public string Fragment /// request. These are similar to switches used in command line shells in that /// they are service-specific. /// - [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [Alias("os")] @@ -774,21 +824,20 @@ 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". /// URI of the resource class/instance representation. /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Resourceuri")] @@ -807,7 +856,7 @@ public Uri ResourceURI /// /// The following is the definition of the input parameter "SelectorSet". /// SelectorSet is a hash table which helps in identify an instance of the - /// management resource if there are are more than 1 instance of the resource + /// management resource if there are more than 1 instance of the resource /// class. /// [Parameter(Position = 1, @@ -1039,7 +1088,10 @@ public string ApplicationName [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -1077,7 +1129,6 @@ public Uri ConnectionURI /// request. These are similar to switches used in command line shells in that /// they are service-specific. /// - [Parameter] [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] [Alias("os")] @@ -1097,21 +1148,20 @@ 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". /// URI of the resource class/instance representation. /// - [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "URI")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Resourceuri")] @@ -1130,7 +1180,7 @@ public Uri ResourceURI /// /// The following is the definition of the input parameter "SelectorSet". /// SelectorSet is a hash table which helps in identify an instance of the - /// management resource if there are are more than 1 instance of the resource + /// management resource if there are more than 1 instance of the resource /// class. /// [Parameter(Position = 1, Mandatory = true, @@ -1275,8 +1325,8 @@ protected override void ProcessRecord() /// Creates an instance of a management resource identified by the resource URI /// 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 { /// @@ -1304,7 +1354,10 @@ public string ApplicationName [Alias("cn")] public string ComputerName { - get { return computername; } + get + { + return computername; + } set { @@ -1378,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". @@ -1408,7 +1461,7 @@ public Uri ResourceURI /// /// The following is the definition of the input parameter "SelectorSet". /// SelectorSet is a hash table which helps in identify an instance of the - /// management resource if there are are more than 1 instance of the resource + /// management resource if there are more than 1 instance of the resource /// class. /// [Parameter(Mandatory = true, Position = 1, @@ -1475,7 +1528,7 @@ public Hashtable ValueSet private Hashtable valueset; private WSManHelper helper; - private IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); + private readonly IWSManEx m_wsmanObject = (IWSManEx)new WSManClass(); private IWSManSession m_session = null; private string connectionStr = string.Empty; diff --git a/src/Microsoft.WSMan.Management/WsManHelper.cs b/src/Microsoft.WSMan.Management/WsManHelper.cs index f09ba2bb233..9249c7f4b88 100644 --- a/src/Microsoft.WSMan.Management/WsManHelper.cs +++ b/src/Microsoft.WSMan.Management/WsManHelper.cs @@ -22,7 +22,7 @@ namespace Microsoft.WSMan.Management { [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] - internal class WSManHelper + internal sealed class WSManHelper { // regular expressions private const string PTRN_URI_LAST = @"([a-z_][-a-z0-9._]*)$"; @@ -78,18 +78,18 @@ internal class WSManHelper // string for operation internal string WSManOp = null; - private PSCmdlet cmdletname; - private NavigationCmdletProvider _provider; + private readonly PSCmdlet cmdletname; + private readonly NavigationCmdletProvider _provider; private FileStream _fs; private StreamReader _sr; - private static ResourceManager _resourceMgr = new ResourceManager("Microsoft.WSMan.Management.resources.WsManResources", typeof(WSManHelper).GetTypeInfo().Assembly); + private static readonly ResourceManager _resourceMgr = new ResourceManager("Microsoft.WSMan.Management.resources.WsManResources", typeof(WSManHelper).GetTypeInfo().Assembly); // // // Below class is just a static container which would release sessions in case this DLL is unloaded. - internal class Sessions + internal sealed class Sessions { /// /// Dictionary object to store the connection. @@ -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; @@ -467,7 +451,7 @@ internal string ProcessInput(IWSManEx wsman, string filepath, string operation, } else { - XmlNode tmpNode = node.ChildNodes[0];//.Item[0]; + XmlNode tmpNode = node.ChildNodes[0]; //.Item[0]; if (!tmpNode.NodeType.ToString().Equals("text", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException(_resourceMgr.GetString("NOAttributeMatch")); @@ -562,7 +546,7 @@ internal IWSManResourceLocator InitializeResourceLocator(Hashtable optionset, Ha if (selectorset != null) { - resource = resource + "?"; + resource += "?"; int i = 0; foreach (DictionaryEntry entry in selectorset) { @@ -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); @@ -656,7 +640,7 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM { if (authentication.Equals(AuthenticationMechanism.None)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseNoAuthentication; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseNoAuthentication; } if (authentication.Equals(AuthenticationMechanism.Basic)) @@ -666,12 +650,12 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM if (authentication.Equals(AuthenticationMechanism.Negotiate)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseNegotiate; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseNegotiate; } if (authentication.Equals(AuthenticationMechanism.Kerberos)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseKerberos; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseKerberos; } if (authentication.Equals(AuthenticationMechanism.Digest)) @@ -686,7 +670,7 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM if (authentication.Equals(AuthenticationMechanism.ClientCertificate)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseClientCertificate; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseClientCertificate; } } @@ -718,7 +702,7 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM connObject.Password = nwCredential.Password; if (!authentication.Equals(AuthenticationMechanism.Credssp) || !authentication.Equals(AuthenticationMechanism.Digest) || authentication.Equals(AuthenticationMechanism.Basic)) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagCredUserNamePassword; + sessionFlags |= (int)WSManSessionFlags.WSManFlagCredUserNamePassword; } } } @@ -726,7 +710,7 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM if (certificateThumbprint != null) { connObject.CertificateThumbprint = certificateThumbprint; - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseClientCertificate; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseClientCertificate; } if (sessionoption != null) @@ -784,48 +768,48 @@ internal IWSManSession CreateSessionObject(IWSManEx wsmanObject, AuthenticationM if (sessionoption.SkipCACheck) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagSkipCACheck; + sessionFlags |= (int)WSManSessionFlags.WSManFlagSkipCACheck; } if (sessionoption.SkipCNCheck) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagSkipCNCheck; + sessionFlags |= (int)WSManSessionFlags.WSManFlagSkipCNCheck; } if (sessionoption.SPNPort > 0) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagEnableSpnServerPort; + sessionFlags |= (int)WSManSessionFlags.WSManFlagEnableSpnServerPort; } if (sessionoption.UseUtf16) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUtf16; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUtf16; } else { // If UseUtf16 is false, then default Encoding is Utf8 - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUtf8; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUtf8; } if (!sessionoption.UseEncryption) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagNoEncryption; + sessionFlags |= (int)WSManSessionFlags.WSManFlagNoEncryption; } if (sessionoption.SkipRevocationCheck) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagSkipRevocationCheck; + sessionFlags |= (int)WSManSessionFlags.WSManFlagSkipRevocationCheck; } } else { // If SessionOption is null then, default Encoding is Utf8 - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUtf8; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUtf8; } if (usessl) { - sessionFlags = sessionFlags | (int)WSManSessionFlags.WSManFlagUseSsl; + sessionFlags |= (int)WSManSessionFlags.WSManFlagUseSsl; } IWSManSession m_SessionObj = null; @@ -871,9 +855,9 @@ internal string GetFilterString(Hashtable seletorset) if (entry.Key != null && entry.Value != null) { filter.Append(entry.Key.ToString()); - filter.Append("="); + filter.Append('='); filter.Append(entry.Value.ToString()); - filter.Append("+"); + filter.Append('+'); } } @@ -917,7 +901,7 @@ internal string GetURIWithFilter(string uri, string filter, Hashtable selectorse { StringBuilder sburi = new StringBuilder(); sburi.Append(uri); - sburi.Append("?"); + sburi.Append('?'); if (operation.Equals("remove", StringComparison.OrdinalIgnoreCase)) { @@ -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); @@ -1092,7 +1073,7 @@ internal static void LoadResourceData() while (!_sr.EndOfStream) { string Line = _sr.ReadLine(); - if (Line.Contains("=")) + if (Line.Contains('=')) { string[] arr = Line.Split('=', count: 2); if (!ResourceValueCache.ContainsKey(arr[0].Trim())) @@ -1135,6 +1116,6 @@ internal static string GetResourceString(string Key) /// /// - private static Dictionary ResourceValueCache = new Dictionary(); + private static readonly Dictionary ResourceValueCache = new Dictionary(); } } 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/Microsoft.WSMan.Runtime/WSManSessionOption.cs b/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs index 014cc03c8ec..13dc8bbea2c 100644 --- a/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs +++ b/src/Microsoft.WSMan.Runtime/WSManSessionOption.cs @@ -2,19 +2,8 @@ // Licensed under the MIT License. using System; -using System.ComponentModel; -using System.IO; -using System.Net; -using System.Reflection; -using System.Resources; -using System.Xml; - -using System.Collections; -using System.Collections.Generic; - -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; +using System.Net; [assembly: CLSCompliant(true)] @@ -23,159 +12,58 @@ namespace Microsoft.WSMan.Management /// /// Session option class. /// - public sealed class SessionOption { /// /// Property. /// - public bool SkipCACheck - { - get { return _SkipCACheck; } - - set - { - _SkipCACheck = value; - } - } - - private bool _SkipCACheck; + public bool SkipCACheck { get; set; } /// /// Property. /// - public bool SkipCNCheck - { - get { return _SkipCNCheck; } - - set - { - _SkipCNCheck = value; - } - } - - private bool _SkipCNCheck; + public bool SkipCNCheck { get; set; } /// /// Property. /// - public bool SkipRevocationCheck - { - get { return _SkipRevocationCheck; } - - set - { - _SkipRevocationCheck = value; - } - } - - private bool _SkipRevocationCheck; + public bool SkipRevocationCheck { get; set; } /// /// Property. /// - public bool UseEncryption - { - get { return _useencryption; } - - set - { - _useencryption = value; - } - } - - private bool _useencryption = true; + public bool UseEncryption { get; set; } = true; /// /// Property. /// - public bool UseUtf16 - { - get { return _UTF16; } - - set - { - _UTF16 = value; - } - } - - private bool _UTF16; + public bool UseUtf16 { get; set; } /// /// Property. /// - public ProxyAuthentication ProxyAuthentication - { - get { return _ProxyAuthentication; } - - set - { - _ProxyAuthentication = value; - } - } - - private ProxyAuthentication _ProxyAuthentication; + public ProxyAuthentication ProxyAuthentication { get; set; } /// /// Property. /// [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "SPN")] - public int SPNPort - { - get { return _SPNPort; } - - set - { - _SPNPort = value; - } - } - - private int _SPNPort; + public int SPNPort { get; set; } /// /// Property. /// - public int OperationTimeout - { - get { return _OperationTimeout; } - - set - { - _OperationTimeout = value; - } - } - - private int _OperationTimeout; + public int OperationTimeout { get; set; } /// /// Property. /// - public NetworkCredential ProxyCredential - { - get { return _ProxyCredential; } - - set - { - _ProxyCredential = value; - } - } - - private NetworkCredential _ProxyCredential; + public NetworkCredential ProxyCredential { get; set; } /// /// Property. /// - public ProxyAccessType ProxyAccessType - { - get { return _proxyaccesstype; } - - set - { - _proxyaccesstype = value; - } - } - - private ProxyAccessType _proxyaccesstype; + public ProxyAccessType ProxyAccessType { get; set; } } /// diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index 613e3d5ce49..3903b164b09 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -1,14 +1,22 @@ - + + PowerShell + Microsoft Corporation + (c) Microsoft Corporation. + + net11.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 33823700f7f..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,26 +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.' - } - ) - } -} +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 cd12b64d034..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,27 +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.' - } - ) - } -} +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/Windows/PSDiagnostics/PSDiagnostics.psm1 b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 index ce0739eb622..c31e9f25963 100644 --- a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 +++ b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 @@ -4,21 +4,22 @@ <# PowerShell Diagnostics Module This module contains a set of wrapper scripts that - enable a user to use ETW tracing in Windows - PowerShell. + enable a user to use ETW tracing in PowerShell 7. #> -$script:Logman="$env:windir\system32\logman.exe" -$script:wsmanlogfile = "$env:windir\system32\wsmtraces.log" -$script:wsmprovfile = "$env:windir\system32\wsmtraceproviders.txt" +$script:windir = [System.Environment]::GetEnvironmentVariable("windir", [System.EnvironmentVariableTarget]::Machine) + +$script:Logman = "${script:windir}\system32\logman.exe" +$script:wsmanlogfile = "${script:windir}\system32\wsmtraces.log" +$script:wsmprovfile = "${script:windir}\system32\wsmtraceproviders.txt" $script:wsmsession = "wsmlog" $script:pssession = "PSTrace" -$script:psprovidername="Microsoft-Windows-PowerShell" +$script:psprovidername = "PowerShellCore" $script:wsmprovidername = "Microsoft-Windows-WinRM" $script:oplog = "/Operational" -$script:analyticlog="/Analytic" -$script:debuglog="/Debug" -$script:wevtutil="$env:windir\system32\wevtutil.exe" +$script:analyticlog = "/Analytic" +$script:debuglog = "/Debug" +$script:wevtutil = "${script:windir}\system32\wevtutil.exe" $script:slparam = "sl" $script:glparam = "gl" @@ -169,7 +170,6 @@ function Enable-PSWSManCombinedTrace $provfile = [io.path]::GetTempFilename() - $traceFileName = [string][Guid]::NewGuid() if ($DoNotOverwriteExistingTrace) { $fileName = [string][guid]::newguid() $logfile = $PSHOME + "\\Traces\\PSTrace_$fileName.etl" @@ -177,8 +177,8 @@ function Enable-PSWSManCombinedTrace $logfile = $PSHOME + "\\Traces\\PSTrace.etl" } - "Microsoft-Windows-PowerShell 0 5" | Out-File $provfile -Encoding ascii - "Microsoft-Windows-WinRM 0 5" | Out-File $provfile -Encoding ascii -Append + "$script:psprovidername 0 5" | Out-File $provfile -Encoding ascii + "$script:wsmprovidername 0 5" | Out-File $provfile -Encoding ascii -Append if (!(Test-Path $PSHOME\Traces)) { @@ -192,7 +192,7 @@ function Enable-PSWSManCombinedTrace Start-Trace -SessionName $script:pssession -OutputFilePath $logfile -ProviderFilePath $provfile -ETS - Remove-Item $provfile -Force -ea 0 + Remove-Item $provfile -Force -ErrorAction SilentlyContinue } function Disable-PSWSManCombinedTrace diff --git a/src/Modules/nuget.config b/src/Modules/nuget.config new file mode 100644 index 00000000000..388a65572dd --- /dev/null +++ b/src/Modules/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man index 5d4dd473f4b..bb4e15351e5 100644 --- a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man +++ b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man @@ -121,6 +121,18 @@ value="0x3002" version="1" /> + + + + + + 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 279feede65b..e265bb453c8 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -6,33 +6,12 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("powershell-fuzz-tests,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 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 293c3599267..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; } } @@ -105,7 +109,7 @@ private PowerShellAssemblyLoadContext(string basePaths) #region Fields - private static readonly object s_syncObj = new object(); + private static readonly object s_syncObj = new(); private readonly string[] _probingPaths; private readonly string[] _extensions = new string[] { ".ni.dll", ".dll" }; // CoreCLR type catalog dictionary @@ -114,9 +118,8 @@ private PowerShellAssemblyLoadContext(string basePaths) private readonly Dictionary _coreClrTypeCatalog; private readonly Lazy> _availableDotNetAssemblyNames; - private readonly HashSet _denyListedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase){ - "System.Windows.Forms" - }; + private readonly HashSet _denyListedAssemblies = + new(StringComparer.OrdinalIgnoreCase) { "System.Windows.Forms" }; #if !UNIX private string _winDir; @@ -140,7 +143,7 @@ private PowerShellAssemblyLoadContext(string basePaths) /// Therefore, there is no need to use the full assembly name as the key. Short assembly name is sufficient. /// private static readonly ConcurrentDictionary s_assemblyCache = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + new(StringComparer.OrdinalIgnoreCase); #endregion Fields @@ -233,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) { @@ -344,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. @@ -356,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; @@ -379,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; @@ -393,23 +395,22 @@ private bool TryFindInGAC(AssemblyName assemblyName, out string assemblyFilePath } // Find the assembly under 'gacRoot' and select the latest version. - private bool FindInGac(string gacRoot, AssemblyName assemblyName, out string assemblyPath) + private static bool FindInGac(string gacRoot, AssemblyName assemblyName, out string assemblyPath) { 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.GetDirectories(tempAssemblyDirPath).OrderBy(d => d).LastOrDefault(); + var chosenVersionDirectory = Directory.EnumerateDirectories(tempAssemblyDirPath).Order().LastOrDefault(); if (!string.IsNullOrEmpty(chosenVersionDirectory)) { // Select first or default as the directory will contain only one assembly. If nothing then default is null; - var foundAssemblyPath = Directory.GetFiles(chosenVersionDirectory, $"{assemblyName.Name}*").FirstOrDefault(); + var foundAssemblyPath = Directory.EnumerateFiles(chosenVersionDirectory, $"{assemblyName.Name}*").FirstOrDefault(); if (!string.IsNullOrEmpty(foundAssemblyPath)) { @@ -430,7 +431,7 @@ private bool FindInGac(string gacRoot, AssemblyName assemblyName, out string ass /// /// Try to get the specified assembly from cache. /// - private bool TryGetAssemblyFromCache(AssemblyName assemblyName, out Assembly asmLoaded) + private static bool TryGetAssemblyFromCache(AssemblyName assemblyName, out Assembly asmLoaded) { if (s_assemblyCache.TryGetValue(assemblyName.Name, out asmLoaded)) { @@ -454,7 +455,7 @@ private bool TryGetAssemblyFromCache(AssemblyName assemblyName, out Assembly asm /// AssemblyName of the requested assembly. /// AssemblyName of the loaded assembly. /// - private bool IsAssemblyMatching(AssemblyName requestedAssembly, AssemblyName loadedAssembly) + private static bool IsAssemblyMatching(AssemblyName requestedAssembly, AssemblyName loadedAssembly) { // // We use the same rules as CoreCLR loader to compare the requested assembly and loaded assembly: @@ -501,7 +502,7 @@ private bool IsAssemblyMatching(AssemblyName requestedAssembly, AssemblyName loa /// /// The assembly strong name of a CoreCLR Trusted_Platform_Assembly /// - private Assembly GetTrustedPlatformAssembly(string tpaStrongName) + private static Assembly GetTrustedPlatformAssembly(string tpaStrongName) { // We always depend on the default context to load the TPAs that are recorded in // the type catalog. @@ -509,7 +510,7 @@ private Assembly GetTrustedPlatformAssembly(string tpaStrongName) // it back from the cache of default context. // - If the requested TPA is not loaded yet, then 'Assembly.Load' will make the // default context to load it - AssemblyName assemblyName = new AssemblyName(tpaStrongName); + AssemblyName assemblyName = new(tpaStrongName); Assembly asmLoaded = Assembly.Load(assemblyName); return asmLoaded; } @@ -517,7 +518,7 @@ private Assembly GetTrustedPlatformAssembly(string tpaStrongName) /// /// Throw FileLoadException. /// - private void ThrowFileLoadException(string errorTemplate, params object[] args) + private static void ThrowFileLoadException(string errorTemplate, params object[] args) { string message = string.Format(CultureInfo.CurrentCulture, errorTemplate, args); throw new FileLoadException(message); @@ -526,7 +527,7 @@ private void ThrowFileLoadException(string errorTemplate, params object[] args) /// /// Throw FileNotFoundException. /// - private void ThrowFileNotFoundException(string errorTemplate, params object[] args) + private static void ThrowFileNotFoundException(string errorTemplate, params object[] args) { string message = string.Format(CultureInfo.CurrentCulture, errorTemplate, args); throw new FileNotFoundException(message); @@ -541,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"; } @@ -566,7 +567,7 @@ private static string GetNativeDllSubFolderName(out string ext) /// /// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. /// - public class PowerShellAssemblyLoadContextInitializer + public static class PowerShellAssemblyLoadContextInitializer { /// /// Create a singleton of PowerShellAssemblyLoadContext. @@ -580,12 +581,46 @@ public class PowerShellAssemblyLoadContextInitializer /// Base directory paths that are separated by semicolon ';'. /// They will be the default paths to probe assemblies. /// - public static void SetPowerShellAssemblyLoadContext([MarshalAs(UnmanagedType.LPWStr)]string basePaths) + 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 c6ac57070cb..4cbb346fb14 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,35 +146,96 @@ 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); internal static readonly string ConfigDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); #else // Gets the location for cache and config folders. - internal static readonly string CacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell"; - internal static readonly string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\PowerShell"; + internal static readonly string CacheDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.LocalApplicationData, + @"Microsoft\PowerShell"); + + internal static readonly string ConfigDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.Personal, + @"PowerShell"); + + private static readonly Lazy _isStaSupported = new Lazy(() => + { + int result = Interop.Windows.CoInitializeEx(IntPtr.Zero, Interop.Windows.COINIT_APARTMENTTHREADED); + + // Per COM documentation: Each successful call to CoInitializeEx (including S_FALSE) + // must be balanced by a corresponding call to CoUninitialize. + // - S_OK (0) means we initialized for the first time. + // - S_FALSE (1) means already initialized, but still increments the reference count. + // Both require CoUninitialize to decrement the reference count. + if (result >= 0) + { + Interop.Windows.CoUninitialize(); + } + + 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 List + internal static bool TryDeriveFromCache(string path1, out string result) + { + if (CacheDirectory is null or []) { - "Certificate.format.ps1xml", - "Diagnostics.format.ps1xml", - "DotNetTypes.format.ps1xml", - "Event.format.ps1xml", - "FileSystem.format.ps1xml", - "Help.format.ps1xml", - "HelpV3.format.ps1xml", - "PowerShellCore.format.ps1xml", - "PowerShellTrace.format.ps1xml", - "Registry.format.ps1xml", - "WSMan.format.ps1xml" - }; + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1); + return true; + } + + internal static bool TryDeriveFromCache(string path1, string path2, out string result) + { + if (CacheDirectory is null or []) + { + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1, path2); + return true; + } + + // format files + internal static readonly string[] FormatFileNames = new string[] + { + "Certificate.format.ps1xml", + "Diagnostics.format.ps1xml", + "DotNetTypes.format.ps1xml", + "Event.format.ps1xml", + "FileSystem.format.ps1xml", + "Help.format.ps1xml", + "HelpV3.format.ps1xml", + "PowerShellCore.format.ps1xml", + "PowerShellTrace.format.ps1xml", + "Registry.format.ps1xml", + "WSMan.format.ps1xml" + }; /// /// Some common environment variables used in PS have different @@ -183,43 +250,48 @@ internal static class CommonEnvVariableNames #endif } - /// - /// Remove the temporary directory created for the current process. - /// - internal static void RemoveTemporaryDirectory() + private static string SafeDeriveFromSpecialFolder(Environment.SpecialFolder specialFolder, string subPath) { - if (_tempDirectory == null) + string basePath = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.DoNotVerify); + if (string.IsNullOrWhiteSpace(basePath)) { - return; + return string.Empty; } - try - { - Directory.Delete(_tempDirectory, true); - } - catch - { - // ignore if there is a failure - } - - _tempDirectory = null; + return Path.Join(basePath, subPath); } +#if UNIX + private static string s_tempHome = null; + /// - /// Get a temporary directory to use for the current process. + /// Get the 'HOME' environment variable or create a temporary home directory if the environment variable is not set. /// - internal static string GetTemporaryDirectory() + 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 envHome; + } + + try { - return _tempDirectory; + s_tempHome = Path.Combine(Path.GetTempPath(), StringUtil.Format(tempHomeFolderName, Environment.UserName)); + Directory.CreateDirectory(s_tempHome); + } + 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. + s_tempHome = string.Empty; } - _tempDirectory = PsUtils.GetTemporaryDirectory(); - return _tempDirectory; + return s_tempHome; } -#if UNIX /// /// X Desktop Group configuration type enum. /// @@ -242,224 +314,97 @@ public enum XDG_Type /// /// 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) - { - envHome = GetTemporaryDirectory(); - } + 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) + return folder switch { - envHome = Platform.GetTemporaryDirectory(); - } - - switch (folder) - { - 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, Environment.SpecialFolderOption.DoNotVerify); #endif - return folderPath ?? string.Empty; } // Platform methods prefixed NonWindows are: @@ -472,21 +417,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); @@ -529,11 +464,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; @@ -554,6 +487,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. @@ -561,10 +504,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 Dictionary(); - private static Dictionary groupnameCache = new Dictionary(); + private static readonly Dictionary usernameCache = new(); + private static readonly Dictionary groupnameCache = new(); /// The type of a Unix file system item. public enum ItemType @@ -694,82 +637,73 @@ 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 Dictionary() - { - { 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 Dictionary() + 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; } + UnixFileMode modeInfo = (UnixFileMode)Mode; + + Span modeCharacters = [ + itemTypeTable[ItemType], + + modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetUser) ? + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetGroup) ? + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.StickyBit) ? + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? StickyAndExec : StickyAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? CanExecute : NoPerm), + ]; + return new string(modeCharacters); } @@ -820,15 +754,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. @@ -857,7 +782,7 @@ public static bool IsHardLink(FileSystemInfo fs) /// A managed common stat class instance. private static CommonStat CopyStatStruct(NativeMethods.CommonStatStruct css) { - CommonStat cs = new CommonStat(); + CommonStat cs = new(); cs.Inode = css.Inode; cs.Mode = css.Mode; cs.UserId = css.UserId; @@ -969,20 +894,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) { @@ -991,31 +936,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). @@ -1062,33 +1016,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. @@ -1156,27 +1102,27 @@ internal struct CommonStatStruct /// This filesystem item is a socket. internal int IsSocket; - /// This filesystem item will run as the the owner if executed. + /// This filesystem item will run as the owner if executed. internal int IsSetUid; - /// This filesystem item will run as the the group if executed. + /// This filesystem item will run as the group if executed. internal int IsSetGid; /// Whether the sticky bit is set on the filesystem item. 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 2691e168364..e439cc30ff2 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -131,253 +131,6 @@ public void Dispose() { } } } -namespace System.Management.Automation.ComInterop -{ - using System.Dynamic; - using System.Diagnostics; - using System.Runtime.InteropServices; - - /// - /// Provides helper methods to bind COM objects dynamically. - /// - /// - /// COM is not supported in core powershell. So this is a stub type. - /// - internal static class ComBinder - { - /// - /// Tries to perform binding of the dynamic get index operation. - /// - /// - /// Always return false in CoreCLR. - /// - public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) - { - result = null; - return false; - } - - /// - /// Tries to perform binding of the dynamic set index operation. - /// - /// - /// Always return false in CoreCLR. - /// - public static bool TryBindSetIndex(SetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, DynamicMetaObject value, out DynamicMetaObject result) - { - result = null; - return false; - } - - /// - /// Tries to perform binding of the dynamic get member operation. - /// - /// - /// Always return false in CoreCLR. - /// - public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result) - { - result = null; - return false; - } - - /// - /// Tries to perform binding of the dynamic set member operation. - /// - /// - /// Always return false in CoreCLR. - /// - public static bool TryBindSetMember(SetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject value, out DynamicMetaObject result) - { - result = null; - return false; - } - - /// - /// Tries to perform binding of the dynamic invoke member operation. - /// - /// - /// Always return false in CoreCLR. - /// - public static bool TryBindInvokeMember(InvokeMemberBinder binder, bool isSetProperty, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) - { - result = null; - return false; - } - } - -#pragma warning disable 618 // Disable obsolete warning about VarEnum in CoreCLR - internal class VarEnumSelector - { - private static readonly Dictionary _ComToManagedPrimitiveTypes = CreateComToManagedPrimitiveTypes(); - - internal static Type GetTypeForVarEnum(VarEnum vt) - { - Type type; - - switch (vt) - { - // VarEnums which can be used in VARIANTs, but which cannot occur in a TYPEDESC - case VarEnum.VT_EMPTY: - case VarEnum.VT_NULL: - case VarEnum.VT_RECORD: - type = typeof(void); - break; - - // VarEnums which are not used in VARIANTs, but which can occur in a TYPEDESC - case VarEnum.VT_VOID: - type = typeof(void); - break; - - case VarEnum.VT_HRESULT: - type = typeof(int); - break; - - case ((VarEnum)37): // VT_INT_PTR: - type = typeof(IntPtr); - break; - - case ((VarEnum)38): // VT_UINT_PTR: - type = typeof(UIntPtr); - break; - - case VarEnum.VT_SAFEARRAY: - case VarEnum.VT_CARRAY: - type = typeof(Array); - break; - - case VarEnum.VT_LPSTR: - case VarEnum.VT_LPWSTR: - type = typeof(string); - break; - - case VarEnum.VT_PTR: - case VarEnum.VT_USERDEFINED: - type = typeof(object); - break; - - // For VarEnums that can be used in VARIANTs and well as TYPEDESCs, just use VarEnumSelector - default: - type = VarEnumSelector.GetManagedMarshalType(vt); - break; - } - - return type; - } - - /// - /// Gets the managed type that an object needs to be coverted 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 - /// returns a simple mapping that is needed for the current implementation. The reason for the - /// many-to-many relation is: - /// 1. Int32 maps to VT_I4 as well as VT_ERROR, and Decimal maps to VT_DECIMAL and VT_CY. However, - /// this changes if you throw the wrapper types into the mix. - /// 2. There is no Type to represent COM types. __ComObject is a private type, and Object is too - /// general. - /// - internal static Type GetManagedMarshalType(VarEnum varEnum) - { - Debug.Assert((varEnum & VarEnum.VT_BYREF) == 0); - - if (varEnum == VarEnum.VT_CY) - { - return typeof(CurrencyWrapper); - } - - if (IsPrimitiveType(varEnum)) - { - return _ComToManagedPrimitiveTypes[varEnum]; - } - - switch (varEnum) - { - case VarEnum.VT_EMPTY: - case VarEnum.VT_NULL: - case VarEnum.VT_UNKNOWN: - case VarEnum.VT_DISPATCH: - case VarEnum.VT_VARIANT: - return typeof(object); - - case VarEnum.VT_ERROR: - return typeof(ErrorWrapper); - - default: - throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, ParserStrings.UnexpectedVarEnum, varEnum)); - } - } - - private static Dictionary CreateComToManagedPrimitiveTypes() - { - Dictionary dict = new Dictionary(); - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ComToManagedPrimitiveTypes from: generate_comdispatch.py - - dict[VarEnum.VT_I1] = typeof(sbyte); - dict[VarEnum.VT_I2] = typeof(Int16); - dict[VarEnum.VT_I4] = typeof(Int32); - dict[VarEnum.VT_I8] = typeof(Int64); - dict[VarEnum.VT_UI1] = typeof(byte); - dict[VarEnum.VT_UI2] = typeof(UInt16); - dict[VarEnum.VT_UI4] = typeof(UInt32); - dict[VarEnum.VT_UI8] = typeof(UInt64); - dict[VarEnum.VT_INT] = typeof(Int32); - dict[VarEnum.VT_UINT] = typeof(UInt32); - dict[VarEnum.VT_PTR] = typeof(IntPtr); - dict[VarEnum.VT_BOOL] = typeof(bool); - dict[VarEnum.VT_R4] = typeof(Single); - dict[VarEnum.VT_R8] = typeof(double); - dict[VarEnum.VT_DECIMAL] = typeof(decimal); - dict[VarEnum.VT_DATE] = typeof(DateTime); - dict[VarEnum.VT_BSTR] = typeof(string); - dict[VarEnum.VT_CLSID] = typeof(Guid); - - // *** END GENERATED CODE *** - - dict[VarEnum.VT_CY] = typeof(CurrencyWrapper); - dict[VarEnum.VT_ERROR] = typeof(ErrorWrapper); - - return dict; - } - - /// - /// Primitive types are the basic COM types. It includes valuetypes like ints, but also reference types - /// like BStrs. It does not include composite types like arrays and user-defined COM types (IUnknown/IDispatch). - /// - internal static bool IsPrimitiveType(VarEnum varEnum) - { - switch (varEnum) - { - case VarEnum.VT_I1: - case VarEnum.VT_I2: - case VarEnum.VT_I4: - case VarEnum.VT_I8: - case VarEnum.VT_UI1: - case VarEnum.VT_UI2: - case VarEnum.VT_UI4: - case VarEnum.VT_UI8: - case VarEnum.VT_INT: - case VarEnum.VT_UINT: - case VarEnum.VT_BOOL: - case VarEnum.VT_ERROR: - case VarEnum.VT_R4: - case VarEnum.VT_R8: - case VarEnum.VT_DECIMAL: - case VarEnum.VT_CY: - case VarEnum.VT_DATE: - case VarEnum.VT_BSTR: - - return true; - } - - return false; - } - } -#pragma warning restore 618 -} - namespace Microsoft.PowerShell.Commands.Internal { using System.Security.AccessControl; @@ -590,19 +343,119 @@ internal string GetResourceStringIndirect( #if UNIX +namespace System.Management.Automation.ComInterop +{ + using System.Dynamic; + using System.Runtime.InteropServices; + + /// + /// Provides helper methods to bind COM objects dynamically. + /// + /// + /// COM is not supported on Unix platforms. So this is a stub type. + /// + internal static class ComBinder + { + /// + /// Tries to perform binding of the dynamic get index operation. + /// + /// + /// Always return false in CoreCLR. + /// + public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) + { + result = null; + return false; + } + + /// + /// Tries to perform binding of the dynamic set index operation. + /// + /// + /// Always return false in CoreCLR. + /// + public static bool TryBindSetIndex(SetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, DynamicMetaObject value, out DynamicMetaObject result) + { + result = null; + return false; + } + + /// + /// Tries to perform binding of the dynamic get member operation. + /// + /// + /// Always return false in CoreCLR. + /// + public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result, bool delayInvocation) + { + result = null; + return false; + } + + /// + /// Tries to perform binding of the dynamic set member operation. + /// + /// + /// Always return false in CoreCLR. + /// + public static bool TryBindSetMember(SetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject value, out DynamicMetaObject result) + { + result = null; + return false; + } + + /// + /// Tries to perform binding of the dynamic invoke member operation. + /// + /// + /// Always return false in CoreCLR. + /// + public static bool TryBindInvokeMember(InvokeMemberBinder binder, bool isSetProperty, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) + { + result = null; + return false; + } + } + + internal static class VarEnumSelector + { + internal static Type GetTypeForVarEnum(VarEnum vt) + { + throw new PlatformNotSupportedException(); + } + } +} + 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; @@ -611,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; @@ -621,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, @@ -637,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 58fc81715bb..283ec1bcad8 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 { /// @@ -41,9 +43,7 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins using (System.Management.Automation.PowerShell powerShell = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace)) { - string script = "param($targetType,$moduleName) & (Microsoft.PowerShell.Core\\Get-Module $moduleName) { New-Object $targetType } "; - - powerShell.AddScript(script); + powerShell.AddScript("param($targetType,$moduleName) & (Microsoft.PowerShell.Core\\Get-Module $moduleName) { New-Object $targetType } "); powerShell.AddArgument(targetType); powerShell.AddArgument(moduleName); @@ -73,7 +73,9 @@ public static object ConvertCimInstanceToObject(Type targetType, CimInstance ins MemberInfo[] memberInfo = targetType.GetMember(property.Name, BindingFlags.Public | BindingFlags.Instance); // verify property exists in corresponding class type - if (memberInfo == null || memberInfo.Length > 1 || !(memberInfo[0] is PropertyInfo || memberInfo[0] is FieldInfo)) + if (memberInfo == null + || memberInfo.Length > 1 + || (memberInfo[0] is not PropertyInfo && memberInfo[0] is not FieldInfo)) { errorMessage = string.Format(CultureInfo.CurrentCulture, ParserStrings.PropertyNotDeclaredInPSClass, new object[] { property.Name, className }); var invalidOperationException = new InvalidOperationException(errorMessage); @@ -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,9 @@ 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; + /// /// internal CimDSCParser(CimMofDeserializer.OnClassNeeded onClassNeeded) @@ -455,7 +453,7 @@ internal List ParseSchemaMof(string filePath) } /// - /// Make sure that the instance conforms to the the schema. + /// Make sure that the instance conforms to the schema. /// /// internal void ValidateInstanceText(string classText) @@ -527,11 +525,8 @@ public DscClassCacheEntry(DSCResourceRunAsCredential aDSCResourceRunAsCredential public static class DscClassCache { private const string InboxDscResourceModulePath = "WindowsPowershell\\v1.0\\Modules\\PsDesiredStateConfiguration"; - private const string reservedDynamicKeywords = "^(Synchronization|Certificate|IIS|SQL)$"; - - private const string reservedProperties = "^(Require|Trigger|Notify|Before|After|Subscribe)$"; - private static 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; @@ -541,19 +536,19 @@ public static class DscClassCache // Create a list of classes which are not actual DSC resources similar to what we do inside PSDesiredStateConfiguration.psm1 private static readonly string[] s_hiddenResourceList = - { - "MSFT_BaseConfigurationProviderRegistration", - "MSFT_CimConfigurationProviderRegistration", - "MSFT_PSConfigurationProviderRegistration", - }; + { + "MSFT_BaseConfigurationProviderRegistration", + "MSFT_CimConfigurationProviderRegistration", + "MSFT_PSConfigurationProviderRegistration", + }; // 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(s_hiddenResourceList, - StringComparer.OrdinalIgnoreCase); + private static readonly HashSet s_hiddenResourceCache = + new(s_hiddenResourceList, StringComparer.OrdinalIgnoreCase); // a collection to hold current importing script based resource file // this prevent circular importing case when the script resource existing in the same module with resources it import-dscresource - private static readonly HashSet s_currentImportingScriptFiles = new HashSet(StringComparer.OrdinalIgnoreCase); + private static readonly HashSet s_currentImportingScriptFiles = new(StringComparer.OrdinalIgnoreCase); /// /// DSC class cache for this runspace. @@ -563,10 +558,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; } @@ -582,10 +574,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; } @@ -601,10 +590,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; } @@ -620,10 +606,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; } @@ -635,18 +618,20 @@ private static HashSet ScriptKeywordFileCache /// /// Default ModuleName and ModuleVersion to use. /// - private static readonly Tuple s_defaultModuleInfoForResource = new Tuple("PSDesiredStateConfiguration", new Version("1.1")); + private static readonly Tuple s_defaultModuleInfoForResource = + new("PSDesiredStateConfiguration", new Version("1.1")); /// /// Default ModuleName and ModuleVersion to use for meta configuration resources. /// - internal static readonly Tuple DefaultModuleInfoForMetaConfigResource = new Tuple("PSDesiredStateConfigurationEngine", new Version("2.0")); + internal static readonly Tuple DefaultModuleInfoForMetaConfigResource = + new("PSDesiredStateConfigurationEngine", new Version("2.0")); /// /// A set of dynamic keywords that can be used in both configuration and meta configuration. /// internal static readonly HashSet SystemResourceNames = - new HashSet(StringComparer.OrdinalIgnoreCase) { "Node", "OMI_ConfigurationDocument" }; + new(StringComparer.OrdinalIgnoreCase) { "Node", "OMI_ConfigurationDocument" }; /// /// When this property is set to true, DSC Cache will cache multiple versions of a resource. @@ -721,7 +706,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); } @@ -738,12 +723,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"); @@ -773,7 +758,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); } @@ -781,7 +766,7 @@ public static void Initialize(Collection errors, List moduleP // Load Regular and DSC PS modules bool importInBoxResourcesImplicitly = false; - List modulePaths = new List(); + List modulePaths = new(); if (modulePathList == null || modulePathList.Count == 0) { modulePaths.Add(Path.Combine(configSystemPath, inboxModulePath)); @@ -819,7 +804,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)) @@ -930,7 +918,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; @@ -940,6 +928,20 @@ private static CimClass MyClassCallback(string serverName, string namespaceName, return null; } + /// + /// Reads CIM MOF schema file and returns classes defined in it. + /// This is used MOF->PSClass conversion tool. + /// + /// + /// Path to CIM MOF schema file for reading. + /// + /// List of classes from MOF schema file. + public static List ReadCimSchemaMof(string mofPath) + { + var parser = new Microsoft.PowerShell.DesiredStateConfiguration.CimDSCParser(MyClassCallback); + return parser.ParseSchemaMof(mofPath); + } + /// /// Import CIM classes from the given file. /// @@ -968,10 +970,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) @@ -993,15 +992,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); } } @@ -1034,7 +1030,7 @@ public static List ImportClasses(string path, Tuple m foreach (var c in classes) { sb.Append(c.CimSystemProperties.ClassName); - sb.Append(","); + sb.Append(','); } s_tracer.WriteLine("DSC ClassCache: loading file '{0}' added the following classes to the cache: {1}", path, sb.ToString()); @@ -1090,7 +1086,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}"); } /// @@ -1103,7 +1099,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] @@ -1128,8 +1124,8 @@ private static List GetCachedClasses() /// List of cached cim classes. public static List GetCachedClassesForModule(PSModuleInfo module) { - List cachedClasses = new List(); - var moduleQualifiedName = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", module.Name, module.Version.ToString()); + List cachedClasses = new(); + var moduleQualifiedName = string.Create(CultureInfo.InvariantCulture, $"{module.Name}\\{module.Version}"); foreach (var dscClassCacheEntry in ClassCache) { if (dscClassCacheEntry.Key.StartsWith(moduleQualifiedName, StringComparison.OrdinalIgnoreCase)) @@ -1148,7 +1144,7 @@ private static List GetCachedClasses() /// public static List GetFileDefiningClass(string className) { - List files = new List(); + List files = new(); foreach (var pair in ByFileClassCache) { var file = pair.Key; @@ -1265,13 +1261,6 @@ public static void ValidateInstanceText(string instanceText) parser.ValidateInstanceText(instanceText); } - private static bool IsMagicProperty(string propertyName) - { - return System.Text.RegularExpressions.Regex.Match(propertyName, - "^(ResourceId|SourceInfo|ModuleName|ModuleVersion|ConfigurationName)$", - System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success; - } - private static string GetFriendlyName(CimClass cimClass) { try @@ -1295,11 +1284,11 @@ private static string GetFriendlyName(CimClass cimClass) /// public static Collection GetCachedKeywords() { - Collection keywords = new Collection(); + Collection keywords = new(); 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]; @@ -1368,7 +1357,8 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi // // Skip all of the base, meta, registration and other classes that are not intended to be used directly by a script author // - if (System.Text.RegularExpressions.Regex.Match(keywordString, "^OMI_Base|^OMI_.*Registration", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (keywordString.StartsWith("OMI_Base", StringComparison.OrdinalIgnoreCase) || + (keywordString.StartsWith("OMI_", StringComparison.OrdinalIgnoreCase) && keywordString.IndexOf("Registration", 4, StringComparison.OrdinalIgnoreCase) >= 0)) { return null; } @@ -1384,7 +1374,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi }; // If it's one of reserved dynamic keyword, mark it - if (System.Text.RegularExpressions.Regex.Match(keywordString, reservedDynamicKeywords, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedDynamicKeyword(keywordString)) { keyword.IsReservedKeyword = true; } @@ -1442,7 +1432,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi } } // If it's one of our reserved properties, save it for error reporting - if (System.Text.RegularExpressions.Regex.Match(prop.Name, reservedProperties, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedProperty(prop.Name)) { keyword.HasReservedProperties = true; continue; @@ -1541,6 +1531,27 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi UpdateKnownRestriction(keyword); return keyword; + + static bool IsMagicProperty(string propertyName) => + string.Equals(propertyName, "ResourceId", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "SourceInfo", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleName", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleVersion", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ConfigurationName", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedDynamicKeyword(string keyword) => + string.Equals(keyword, "Synchronization", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "Certificate", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "IIS", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "SQL", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedProperty(string name) => + string.Equals(name, "Require", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Trigger", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Notify", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Before", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "After", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Subscribe", StringComparison.OrdinalIgnoreCase); } /// @@ -1605,8 +1616,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); @@ -1879,10 +1890,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", @@ -1906,7 +1914,7 @@ private static ParseError[] ImportResourceCheckSemantics(DynamicKeywordStatement // This function performs semantic checks for all DSC Resources keywords. private static ParseError[] CheckMandatoryPropertiesPresent(DynamicKeywordStatementAst kwAst) { - HashSet mandatoryPropertiesNames = new HashSet(StringComparer.OrdinalIgnoreCase); + HashSet mandatoryPropertiesNames = new(StringComparer.OrdinalIgnoreCase); foreach (var pair in kwAst.Keyword.Properties) { if (pair.Value.Mandatory) @@ -1939,8 +1947,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) { @@ -2026,7 +2033,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))); @@ -2134,8 +2141,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, { try { - string unused; - foundResources = ImportCimKeywordsFromModule(moduleInfo, resourceToImport, out unused); + foundResources = ImportCimKeywordsFromModule(moduleInfo, resourceToImport, out _); } catch (Exception) { @@ -2143,7 +2149,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, } // resource name without wildcard (*) should be imported only once - if (!resourceToImport.Contains("*") && foundResources) + if (!resourceToImport.Contains('*') && foundResources) { resourcesFound.Add(resourceToImport); } @@ -2165,7 +2171,7 @@ public static void LoadResourcesFromModule(IScriptExtent scriptExtent, { foreach (var resourceNameToImport in resourcesToImport) { - if (!resourceNameToImport.Contains("*")) + if (!resourceNameToImport.Contains('*')) { errorList.Add(new ParseError(scriptExtent, "DscResourcesNotFoundDuringParsing", @@ -2303,8 +2309,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; @@ -2327,7 +2332,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"; @@ -2347,9 +2352,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"); } @@ -2358,7 +2363,7 @@ private static void GenerateMofForAst(TypeDefinitionAst typeAst, StringBuilder s ProcessMembers(sb, embeddedInstanceTypes, typeAst, className); - Queue bases = new Queue(); + Queue bases = new(); foreach (var b in typeAst.BaseTypes) { bases.Enqueue(b); @@ -2367,15 +2372,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) @@ -2417,8 +2420,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)) { @@ -2456,7 +2458,7 @@ public static bool GetResourceMethodsLinePosition(PSModuleInfo moduleInfo, strin } IEnumerable resourceDefinitions; - List moduleFiles = new List(); + List moduleFiles = new(); if (moduleInfo.RootModule != null) { moduleFiles.Add(moduleInfo.Path); @@ -2464,7 +2466,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); } @@ -2498,9 +2500,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; @@ -2540,14 +2540,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"); } } @@ -2590,7 +2588,7 @@ private static bool GetResourceDefinitionsFromModule(string fileName, out IEnume { if (errorList != null && extent != null) { - List errorMessages = new List(); + List errorMessages = new(); foreach (var error in errors) { errorMessages.Add(error.ToString()); @@ -2605,13 +2603,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; + } } } @@ -2665,7 +2665,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; @@ -2675,13 +2678,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; } } } @@ -2695,19 +2694,19 @@ private static bool ImportKeywordsFromScriptFile(string fileName, PSModuleInfo m return result; } - private static readonly Dictionary s_mapPrimitiveDotNetTypeToMof = new Dictionary() + 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" }, @@ -2911,12 +2910,11 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable { var sb = new StringBuilder(); - sb.Append("["); + sb.Append('['); bool needComma = false; foreach (var attr in customAttributes) { - var dscProperty = attr as DscPropertyAttribute; - if (dscProperty != null) + if (attr is DscPropertyAttribute dscProperty) { if (dscProperty.Key) { @@ -2939,11 +2937,10 @@ 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 StringBuilder(", Values{"); + StringBuilder sbValues = new(", Values{"); sb.AppendFormat(CultureInfo.InvariantCulture, "{0}ValueMap{{", needComma ? ", " : string.Empty); needComma = true; @@ -2954,9 +2951,9 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable valueMapComma = true; } - sb.Append("}"); + sb.Append('}'); sb.Append(sbValues); - sb.Append("}"); + sb.Append('}'); } } @@ -2985,14 +2982,14 @@ private static string MapAttributesToMof(string[] enumNames, IEnumerable needComma = true; } - sb.Append("}"); + sb.Append('}'); } else if (embeddedInstanceType != null) { sb.AppendFormat(CultureInfo.InvariantCulture, "{0}EmbeddedInstance(\"{1}\")", needComma ? ", " : string.Empty, embeddedInstanceType); } - sb.Append("]"); + sb.Append(']'); return sb.ToString(); } @@ -3055,7 +3052,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()) { @@ -3070,9 +3067,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; } @@ -3095,21 +3092,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"); } } @@ -3124,7 +3121,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) { @@ -3140,7 +3137,10 @@ private static bool ImportKeywordsFromAssembly(PSModuleInfo module, } } - if (skip) continue; + if (skip) + { + continue; + } var mof = GenerateMofForType(r); @@ -3258,14 +3258,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) { @@ -3618,7 +3619,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)) { @@ -3645,7 +3646,7 @@ public static string GetDSCResourceUsageString(DynamicKeyword keyword) } } - usageString.Append("}"); + usageString.Append('}'); return usageString.ToString(); } @@ -3659,7 +3660,7 @@ public static string GetDSCResourceUsageString(DynamicKeyword keyword) private static StringBuilder FormatCimPropertyType(DynamicKeywordProperty prop, bool isOptionalProperty) { string cimTypeName = prop.TypeConstraint; - StringBuilder formattedTypeString = new StringBuilder(); + StringBuilder formattedTypeString = new(); if (string.Equals(cimTypeName, "MSFT_Credential", StringComparison.OrdinalIgnoreCase)) { @@ -3685,16 +3686,16 @@ 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 if (isOptionalProperty) { - formattedTypeString.Append("]"); + formattedTypeString.Append(']'); } - formattedTypeString.Append("\n"); + formattedTypeString.Append('\n'); return formattedTypeString; } @@ -3707,8 +3708,7 @@ private static ScriptBlock CimKeywordImplementationFunction get { // The scriptblock cache will handle mutual exclusion - return s_cimKeywordImplementationFunction ?? - (s_cimKeywordImplementationFunction = ScriptBlock.Create(CimKeywordImplementationFunctionText)); + return s_cimKeywordImplementationFunction ??= ScriptBlock.Create(CimKeywordImplementationFunctionText); } } 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..4c8d23971af 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; namespace System.Management.Automation.Runspaces { @@ -42,27 +43,27 @@ 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: String.Format(CultureInfo.CurrentCulture, "{0:d} {0:HH}:{0:mm}", CultureInfo.CurrentCulture.Calendar.MaxSupportedDateTime).Length) + .AddHeader(Alignment.Right, label: "Size", width: 12) + .AddHeader(Alignment.Left, label: "Name") + .StartRowDefinition(wrap: true) + .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 987ed33fb4a..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, @@ -525,6 +525,9 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddText(HelpDisplayStrings.ParameterIsDynamic) .AddPropertyExpressionBinding(@"isDynamic") .AddNewline() + .AddText(HelpDisplayStrings.AcceptsWildCardCharacters) + .AddPropertyExpressionBinding(@"globbing") + .AddNewline() .AddNewline() .EndFrame() .EndEntry() @@ -711,6 +714,9 @@ private static IEnumerable ViewsOf_ExtendedCmdletHelpInfo_ .AddText(HelpDisplayStrings.ParameterIsDynamic) .AddPropertyExpressionBinding(@"isDynamic") .AddNewline() + .AddText(HelpDisplayStrings.AcceptsWildCardCharacters) + .AddPropertyExpressionBinding(@"globbing") + .AddNewline() .AddNewline() .EndFrame() .EndEntry() 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..6825ec14e6c 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() @@ -677,10 +680,7 @@ private static IEnumerable ViewsOf_MamlCommandHelpInfo_Ful .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"title") .AddNewline() - .AddNewline() - .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) - .EndFrame() + .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) .AddNewline() .EndFrame() .EndEntry() diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index dfd610e4750..24d99d4dd4b 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -133,6 +133,14 @@ internal static IEnumerable GetFormatData() "System.Management.Automation.PSDriveInfo", ViewsOf_System_Management_Automation_PSDriveInfo()); + yield return new ExtendedTypeDefinition( + "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()); @@ -244,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()); @@ -259,6 +271,30 @@ internal static IEnumerable GetFormatData() yield return new ExtendedTypeDefinition( "Microsoft.PowerShell.Commands.ByteCollection", ViewsOf_Microsoft_PowerShell_Commands_ByteCollection()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle", + ViewsOf_System_Management_Automation_PSStyle()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+FormattingData", + ViewsOf_System_Management_Automation_PSStyleFormattingData()); + + yield return new ExtendedTypeDefinition( + "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()); + + yield return new ExtendedTypeDefinition( + "System.Management.Automation.PSStyle+BackgroundColor", + ViewsOf_System_Management_Automation_PSStyleBackgroundColor()); } private static IEnumerable ViewsOf_System_RuntimeType() @@ -679,7 +715,6 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemProperty(@"Description") .AddItemProperty(@"Capabilities") .AddItemProperty(@"ImplementingType") - .AddItemProperty(@"AssemblyInfo") .EndEntry() .EndList()); } @@ -693,7 +728,6 @@ private static IEnumerable ViewsOf_System_Management_Autom .AddItemProperty(@"CommandType") .AddItemProperty(@"Definition") .AddItemProperty(@"Path") - .AddItemProperty(@"AssemblyInfo") .AddItemProperty(@"DLL") .AddItemProperty(@"HelpFile") .AddItemProperty(@"ParameterSets") @@ -730,6 +764,39 @@ private static IEnumerable ViewsOf_System_Management_Autom .EndList()); } + private static IEnumerable ViewsOf_System_Management_Automation_Subsystem_SubsystemInfo() + { + yield return new FormatViewDefinition( + "System.Management.Automation.Subsystem.SubsystemInfo", + TableControl.Create() + .AddHeader(Alignment.Left, width: 17, label: "Kind") + .AddHeader(Alignment.Left, width: 18, label: "SubsystemType") + .AddHeader(Alignment.Right, width: 12, label: "IsRegistered") + .AddHeader(Alignment.Left, label: "Implementations") + .StartRowDefinition() + .AddPropertyColumn("Kind") + .AddScriptBlockColumn("$_.SubsystemType.Name") + .AddPropertyColumn("IsRegistered") + .AddPropertyColumn("Implementations") + .EndRowDefinition() + .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", @@ -766,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' @@ -861,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) } @@ -889,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 ' : ' @@ -955,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) @@ -1005,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 - ) - } + $resetColor = '' + $errorColor = '' + $accentColor = '' - 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"" - - $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) { @@ -1051,44 +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 = '' - if ($myinv -and $myinv.ScriptName -or $myinv.ScriptLineNumber -gt 1 -or $err.CategoryInfo.Category -eq 'ParserError') { - $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 @@ -1105,13 +1185,42 @@ function Get-ConciseViewPositionMessage { } $verticalBar = '|' - $posmsg += ""${accentColor}${headerWhitespace}Line ${verticalBar}${newline}"" + $posmsg += "${accentColor}${headerWhitespace}Line ${verticalBar}${newline}" $highlightLine = '' if ($useTargetObject) { $line = $_.TargetObject.LineText.Trim() + $offsetLength = 0 $offsetInLine = 0 + $startColumn = 0 + if ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('StartColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.StartColumn, [ref]$startColumn) -and + $null -ne $startColumn -and + $startColumn -gt 0 -and + $startColumn -le $line.Length + ) { + $endColumn = 0 + if (-not ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('EndColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.EndColumn, [ref]$endColumn) -and + $null -ne $endColumn -and + $endColumn -gt $startColumn -and + $endColumn -le ($line.Length + 1) + )) { + $endColumn = $line.Length + 1 + } + + # Input is expected to be 1-based index to match the extent positioning + # but we use 0-based indexing below. + $startColumn -= 1 + $endColumn -= 1 + + $highlightLine = "$(" " * $startColumn)$("~" * ($endColumn - $startColumn))" + $offsetLength = $endColumn - $startColumn + $offsetInLine = $startColumn + } } else { $positionMessage = $myinv.PositionMessage.Split($newline) @@ -1130,19 +1239,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 @@ -1160,11 +1269,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(""`t"", ' ') + $message = $message.Replace($newline, ' ').Replace("`n", ' ').Replace("`t", ' ') $windowWidth = 120 if ($Host.UI.RawUI -ne $null) { @@ -1199,20 +1308,20 @@ function Get-ConciseViewPositionMessage { $message += $newline } - $posmsg += ""${errorColor}"" + $message + $posmsg += "${errorColor}" + $message $reason = 'Error' if ($err.Exception -and $err.Exception.WasThrownFromThrowStatement) { $reason = 'Exception' } # MyCommand can be the script block, so we don't want to show that so check if it's an actual command - elseif ($myinv.MyCommand -and (Get-Command -Name $myinv.MyCommand -ErrorAction Ignore)) + elseif ($myinv.MyCommand -and $myinv.MyCommand.Name -and (Get-Command -Name $myinv.MyCommand -ErrorAction Ignore)) { $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 @@ -1229,7 +1338,7 @@ function Get-ConciseViewPositionMessage { $errorMsg = 'Error' - ""${errorColor}${reason}: ${posmsg}${resetcolor}"" + "${errorColor}${reason}: ${posmsg}${resetcolor}" } $myinv = $_.InvocationInfo @@ -1240,69 +1349,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 + $newline - } else { - $err.ErrorDetails.Message + $posmsg - } + $finalMsg = if ($err.ErrorDetails.Message) { + $err.ErrorDetails.Message + $posmsg + } else { + $err.Exception.Message + $posmsg } - ") + + "${errorColor}${finalMsg}${resetcolor}" + """) .EndEntry() .EndControl()); } @@ -1647,8 +1771,8 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co } private const string PreReleaseStringScriptBlock = @" - if ($_.PrivateData -and - $_.PrivateData.ContainsKey('PSData') -and + if ($_.PrivateData -and + $_.PrivateData.ContainsKey('PSData') -and $_.PrivateData.PSData.ContainsKey('PreRelease')) { $_.PrivateData.PSData.PreRelease @@ -1864,6 +1988,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( @@ -1984,5 +2133,199 @@ private static IEnumerable ViewsOf_Microsoft_PowerShell_Co .GroupByProperty("Label") .EndTable()); } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyle() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Reset)$($_.Reset.Replace(""""`e"""",'`e'))""", label: "Reset") + .AddItemScriptBlock(@"""$($_.BlinkOff)$($_.BlinkOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "BlinkOff") + .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") + .AddItemScriptBlock(@"""$($_.ReverseOff)$($_.ReverseOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "ReverseOff") + .AddItemScriptBlock(@"""$($_.ItalicOff)$($_.ItalicOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "ItalicOff") + .AddItemScriptBlock(@"""$($_.Italic)$($_.Italic.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Italic") + .AddItemScriptBlock(@"""$($_.UnderlineOff)$($_.UnderlineOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "UnderlineOff") + .AddItemScriptBlock(@"""$($_.Underline)$($_.Underline.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Underline") + .AddItemScriptBlock(@"""$($_.StrikethroughOff)$($_.StrikethroughOff.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "StrikethroughOff") + .AddItemScriptBlock(@"""$($_.Strikethrough)$($_.Strikethrough.Replace(""""`e"""",'`e'))$($_.Reset)""", label: "Strikethrough") + .AddItemProperty(@"OutputRendering") + .AddItemScriptBlock(@"""$($_.Formatting.FormatAccent)$($_.Formatting.FormatAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.FormatAccent") + .AddItemScriptBlock(@"""$($_.Formatting.ErrorAccent)$($_.Formatting.ErrorAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.ErrorAccent") + .AddItemScriptBlock(@"""$($_.Formatting.Error)$($_.Formatting.Error.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Formatting.Error") + .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.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.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.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.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.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.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.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.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.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.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.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.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.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.BrightYellow)$($_.Background.BrightYellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Background.BrightYellow") + .EndEntry() + .EndList()); + } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleFormattingData() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+FormattingData", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.FormatAccent)$($_.FormatAccent.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "FormatAccent") + .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: "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()); + } + + private static IEnumerable ViewsOf_System_Management_Automation_PSStyleProgressConfiguration() + { + yield return new FormatViewDefinition("System.Management.Automation.PSStyle+ProgressConfiguration", + ListControl.Create() + .StartEntry() + .AddItemScriptBlock(@"""$($_.Style)$($_.Style.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Style") + .AddItemProperty(@"MaxWidth") + .AddItemProperty(@"View") + .AddItemProperty(@"UseOSCIndicator") + .EndEntry() + .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(@"""$($_.BrightWhite)$($_.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightWhite") + .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") + .AddItemScriptBlock(@"""$($_.BrightRed)$($_.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightRed") + .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") + .AddItemScriptBlock(@"""$($_.BrightMagenta)$($_.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightMagenta") + .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") + .AddItemScriptBlock(@"""$($_.BrightBlue)$($_.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlue") + .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") + .AddItemScriptBlock(@"""$($_.BrightCyan)$($_.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightCyan") + .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") + .AddItemScriptBlock(@"""$($_.BrightGreen)$($_.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightGreen") + .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") + .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+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(@"""$($_.BrightWhite)$($_.BrightWhite.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightWhite") + .AddItemScriptBlock(@"""$($_.Red)$($_.Red.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Red") + .AddItemScriptBlock(@"""$($_.BrightRed)$($_.BrightRed.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightRed") + .AddItemScriptBlock(@"""$($_.Magenta)$($_.Magenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Magenta") + .AddItemScriptBlock(@"""$($_.BrightMagenta)$($_.BrightMagenta.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightMagenta") + .AddItemScriptBlock(@"""$($_.Blue)$($_.Blue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Blue") + .AddItemScriptBlock(@"""$($_.BrightBlue)$($_.BrightBlue.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightBlue") + .AddItemScriptBlock(@"""$($_.Cyan)$($_.Cyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Cyan") + .AddItemScriptBlock(@"""$($_.BrightCyan)$($_.BrightCyan.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightCyan") + .AddItemScriptBlock(@"""$($_.Green)$($_.Green.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Green") + .AddItemScriptBlock(@"""$($_.BrightGreen)$($_.BrightGreen.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "BrightGreen") + .AddItemScriptBlock(@"""$($_.Yellow)$($_.Yellow.Replace(""""`e"""",'`e'))$($PSStyle.Reset)""", label: "Yellow") + .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 e03b4ae61eb..e59cae40146 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseCommand.cs @@ -22,12 +22,13 @@ internal TerminatingErrorContext(PSCmdlet command) _command = command; } + [System.Diagnostics.CodeAnalysis.DoesNotReturn] internal void ThrowTerminatingError(ErrorRecord errorRecord) { _command.ThrowTerminatingError(errorRecord); } - private PSCmdlet _command; + private readonly PSCmdlet _command; } /// @@ -133,7 +134,7 @@ public void Dispose() private string _commandName = null; private Type _commandType; - private List _commandParameterList = new List(); + private readonly List _commandParameterList = new List(); private ExecutionContext _context = null; } @@ -150,7 +151,7 @@ public abstract class FrontEndCommandBase : PSCmdlet, IDisposable /// This parameter specifies the current pipeline object. /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; #endregion @@ -404,4 +405,3 @@ protected virtual void InternalDispose() } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs index ffccf13f4e1..be31a1db9a8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -9,6 +9,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -517,7 +518,7 @@ private void PopGroup() /// /// The formatting shape this formatter emits. /// - private FormatShape _shape; + private readonly FormatShape _shape; #region expression factory @@ -537,7 +538,7 @@ internal ScriptBlock CreateScriptBlock(string scriptText) private TypeInfoDataBase _typeInfoDataBase = null; private FormattingCommandLineParameters _parameters = null; - private FormatViewManager _viewManager = new FormatViewManager(); + private readonly FormatViewManager _viewManager = new FormatViewManager(); private int _enumerationLimit = InitialSessionState.DefaultFormatEnumerationLimit; } @@ -576,7 +577,10 @@ public SwitchParameter ShowError return false; } - set { showErrorsAsMessages = value; } + set + { + showErrorsAsMessages = value; + } } internal bool? showErrorsAsMessages = null; @@ -595,7 +599,10 @@ public SwitchParameter DisplayError return false; } - set { showErrorsInFormattedOutput = value; } + set + { + showErrorsInFormattedOutput = value; + } } internal bool? showErrorsInFormattedOutput = null; @@ -721,8 +728,15 @@ public class OuterFormatTableAndListBase : OuterFormatShapeCommandBase /// will be determined using property sets, etc. /// [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get; set; } + /// + /// Optional parameter for excluding properties from formatting. + /// + [Parameter] + public string[] ExcludeProperty { get; set; } + #endregion internal override FormattingCommandLineParameters GetCommandLineParameters() @@ -745,6 +759,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() internal void GetCommandLineProperties(FormattingCommandLineParameters parameters, bool isTable) { + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((Property is not null && Property.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (Property != null) { CommandParameterDefinition def; @@ -759,15 +785,21 @@ internal void GetCommandLineProperties(FormattingCommandLineParameters parameter parameters.mshParameterList = processor.ProcessParameters(Property, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (Property is null || Property.Length == 0) { - ReportCannotSpecifyViewAndProperty(); - } + CommandParameterDefinition def = isTable + ? new FormatTableParameterDefinition() + : new FormatListParameterDefinition(); + ParameterProcessor processor = new ParameterProcessor(def); + TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); - parameters.viewName = this.View; + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); + } } } } @@ -792,7 +824,10 @@ public SwitchParameter AutoSize return false; } - set { _autosize = value; } + set + { + _autosize = value; + } } private bool? _autosize = null; @@ -817,7 +852,10 @@ public SwitchParameter HideTableHeaders return false; } - set { _hideHeaders = value; } + set + { + _hideHeaders = value; + } } private bool? _hideHeaders = null; @@ -836,7 +874,10 @@ public SwitchParameter Wrap return false; } - set { _multiLine = value; } + set + { + _multiLine = value; + } } private bool? _multiLine = null; @@ -882,4 +923,3 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs index cdd60637a7a..498f6098a24 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs @@ -70,6 +70,11 @@ internal sealed class FormattingCommandLineParameters /// Extension mechanism for shape specific parameters. /// internal ShapeSpecificParameters shapeParameters = null; + + /// + /// Filter for excluding properties from formatting. + /// + internal PSPropertyExpressionFilter excludePropertyFilter = null; } /// @@ -176,15 +181,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)) { @@ -249,7 +252,7 @@ private void ProcessGlobbingCharactersError(bool originalParameterWasHashTable, #endregion - private bool _noGlobbing; + private readonly bool _noGlobbing; } internal class AlignmentEntryDefinition : HashtableEntryDefinition @@ -489,4 +492,3 @@ protected override void SetEntries() } #endregion } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseOutputtingCommand.cs index bb8305b2fdc..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)) { @@ -181,7 +174,7 @@ private FormatShape ActiveFormattingShape { // we assume that the format context // contains the information - FormatShape shape = FormatShape.Table; // default + const FormatShape shape = FormatShape.Table; // default FormatOutputContext foc = this.FormatContext; if (foc == null || foc.Data.shapeInfo == null) @@ -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(); @@ -596,9 +590,9 @@ private void ProcessOutOfBandPayload(FormatEntryData fed) internal LineOutput LineOutput { - set { _lo = value; } - get { return _lo; } + + set { _lo = value; } } private ShapeInfo ShapeInfoOnFormatContext @@ -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; } @@ -637,7 +629,7 @@ private FormatOutputContext FormatContext /// /// Context manager instance to guide the message traversal. /// - private FormatMessagesContextManager _ctxManager = new FormatMessagesContextManager(); + private readonly FormatMessagesContextManager _ctxManager = new FormatMessagesContextManager(); private FormattedObjectsCache _cache = null; @@ -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. @@ -940,7 +928,7 @@ internal TableOutputContextBase(OutCommandInner cmd, /// /// Helper class to properly write a table using text output. /// - private TableWriter _tableWriter = new TableWriter(); + private readonly TableWriter _tableWriter = new TableWriter(); } private sealed class TableOutputContext : TableOutputContextBase @@ -951,7 +939,7 @@ private sealed class TableOutputContext : TableOutputContextBase private const int WhitespaceAndPagerLineCount = 2; - private bool _repeatHeader = false; + private readonly bool _repeatHeader = false; /// /// 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; } /// @@ -1174,7 +1170,7 @@ internal override void ProcessPayload(FormatEntryData fed) /// /// Writer to do the actual formatting. /// - private ListWriter _listWriter = new ListWriter(); + private readonly ListWriter _listWriter = new ListWriter(); } private sealed class WideOutputContext : TableOutputContextBase @@ -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. @@ -1357,7 +1351,7 @@ internal void Reset() _arr[k] = null; } - private string[] _arr; + private readonly string[] _arr; private int _lastEmptySpot; } } @@ -1389,14 +1383,13 @@ 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 ComplexWriter _writer = new ComplexWriter(); + private readonly ComplexWriter _writer = new ComplexWriter(); } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs b/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs index d2e44c4ad60..68ccc7ebc59 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ColumnWidthManager.cs @@ -201,8 +201,8 @@ private static int GetLastVisibleColumn(Span columnWidths) return columnWidths.Length - 1; } - private int _tableWidth; - private int _minimumColumnWidth; - private int _separatorWidth; + private readonly int _tableWidth; + private readonly int _minimumColumnWidth; + private readonly int _separatorWidth; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs index 74031dc515c..a69ad41c965 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Globalization; +using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; @@ -68,8 +69,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 +98,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 +144,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; } @@ -206,7 +204,7 @@ private void WriteToScreen() /// /// Helper object to manage the frame-based indentation and margins. /// - private IndentationManager _indentationManager = new IndentationManager(); + private readonly IndentationManager _indentationManager = new IndentationManager(); /// /// Buffer to accumulate partially constructed text. @@ -237,13 +235,10 @@ internal IndentationStackFrame(IndentationManager mgr) public void Dispose() { - if (_mgr != null) - { - _mgr.RemoveStackFrame(); - } + _mgr?.RemoveStackFrame(); } - private IndentationManager _mgr; + private readonly IndentationManager _mgr; } internal void Clear() @@ -311,7 +306,7 @@ private int ComputeLeftIndentation() return val; } - private Stack _frameInfoStack = new Stack(); + private readonly Stack _frameInfoStack = new Stack(); } /// @@ -321,6 +316,7 @@ internal struct GetWordsResult { internal string Word; internal string Delim; + internal bool VtResetAdded; } /// @@ -328,10 +324,11 @@ 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 static Collection s_cultureCollection = new Collection(); + 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,71 @@ 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); - yield return result; + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + wordHasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + wordHasVtSeqs = true; + } + + i += len - 1; + continue; } - // 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) + { + bool vtResetAdded = false; + if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset)) + { + vtResetAdded = true; + sb.Append(PSStyle.Instance.Reset); + } + var result = new GetWordsResult() + { + Word = sb.ToString(), + Delim = delimiter, + VtResetAdded = vtResetAdded + }; + + sb.Clear().Append(vtSeqs); yield return result; } else @@ -382,10 +423,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 +466,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 +488,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 +496,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 +505,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 +519,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 +528,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; } } @@ -510,10 +566,10 @@ internal int ActiveLen } } - private StringCollection _retVal; + private readonly StringCollection _retVal; private bool _addedFirstLine; - private int _firstLineLen; - private int _followingLinesLen; + private readonly int _firstLineLen; + private readonly int _followingLinesLen; } private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCells, string val, int firstLineLen, int followingLinesLen) @@ -528,9 +584,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 +599,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 = word.VtResetAdded + ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix) + : wordToAdd + suffix; } int wordWidth = displayCells.Length(wordToAdd); @@ -589,15 +651,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 +688,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 +716,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 +746,87 @@ 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(); + + StringBuilder vtSeqs = null; + Dictionary vtRanges = null; - foreach (char c in s) + 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; + 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); - string[] lines = SplitLines (s); + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + hasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + hasVtSeqs = true; + } - if (lines.Length == 0) - return null; + 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 +834,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,9 +848,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 6b647bd0912..2ae4ac2626e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs @@ -19,20 +19,20 @@ namespace System.Management.Automation.Runspaces { /// /// This exception is used by Formattable constructor to indicate errors - /// occured during construction time. + /// occurred during construction time. /// - [Serializable] [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FormatTable")] public class FormatTableLoadException : RuntimeException { - private Collection _errors; + private readonly Collection _errors; #region Constructors /// /// This is the default constructor. /// - public FormatTableLoadException() : base() + public FormatTableLoadException() + : base() { SetDefaultErrorRecord(); } @@ -43,7 +43,8 @@ public FormatTableLoadException() : base() /// /// A localized error message. /// - public FormatTableLoadException(string message) : base(message) + public FormatTableLoadException(string message) + : base(message) { SetDefaultErrorRecord(); } @@ -68,10 +69,10 @@ public FormatTableLoadException(string message, Exception innerException) /// time. /// /// - /// The errors that occured + /// The errors that occurred. /// - internal FormatTableLoadException(ConcurrentBag loadErrors) : - base(StringUtil.Format(FormatAndOutXmlLoadingStrings.FormatTableLoadErrors)) + internal FormatTableLoadException(ConcurrentBag loadErrors) + : base(StringUtil.Format(FormatAndOutXmlLoadingStrings.FormatTableLoadErrors)) { _errors = new Collection(loadErrors.ToArray()); SetDefaultErrorRecord(); @@ -82,56 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. /// @@ -161,7 +120,7 @@ public sealed class FormatTable { #region Private Data - private TypeInfoDataBaseManager _formatDBMgr; + private readonly TypeInfoDataBaseManager _formatDBMgr; #endregion diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs index 02d60731e43..9ebc79a762a 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/XmlLoaderBase.cs @@ -44,7 +44,7 @@ internal class TooManyErrorsException : TypeInfoDataBaseLoaderException /// internal class XmlLoaderLoggerEntry { - internal enum EntryType { Error, Trace }; + internal enum EntryType { Error, Trace } /// /// Type of information being logged. @@ -81,7 +81,7 @@ internal class XmlLoaderLogger : IDisposable #region tracer // PSS/end-user tracer [TraceSource("FormatFileLoading", "Loading format files")] - private static PSTraceSource s_formatFileLoadingtracer = PSTraceSource.GetTracer("FormatFileLoading", "Loading format files", false); + private static readonly PSTraceSource s_formatFileLoadingtracer = PSTraceSource.GetTracer("FormatFileLoading", "Loading format files", false); #endregion tracer /// @@ -100,7 +100,7 @@ internal void LogEntry(XmlLoaderLoggerEntry entry) WriteToTracer(entry); } - private void WriteToTracer(XmlLoaderLoggerEntry entry) + private static void WriteToTracer(XmlLoaderLoggerEntry entry) { if (entry.entryType == XmlLoaderLoggerEntry.EntryType.Error) { @@ -149,12 +149,12 @@ internal bool HasErrors /// /// If true, log entries to memory. /// - private bool _saveInMemory = true; + private readonly bool _saveInMemory = true; /// /// List of entries logged if saveInMemory is true. /// - private List _entries = new List(); + private readonly List _entries = new List(); /// /// True if we ever logged an error. @@ -170,7 +170,7 @@ internal abstract class XmlLoaderBase : IDisposable { #region tracer [TraceSource("XmlLoaderBase", "XmlLoaderBase")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("XmlLoaderBase", "XmlLoaderBase"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("XmlLoaderBase", "XmlLoaderBase"); #endregion tracer /// @@ -378,20 +378,16 @@ private bool MatchNodeNameHelper(XmlNode n, string s, bool allowAttributes) // we differ only in case: flag this as an ERROR for the time being // and accept the comparison - string fmtString = "XML tag differ in case only {0} {1}"; + const string fmtString = "XML tag differ in case only {0} {1}"; ReportTrace(string.Format(CultureInfo.InvariantCulture, fmtString, n.Name, s)); 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; @@ -420,7 +416,7 @@ internal bool MatchAttributeName(XmlAttribute a, string s) // we differ only in case: flag this as an ERROR for the time being // and accept the comparison - string fmtString = "XML attribute differ in case only {0} {1}"; + const string fmtString = "XML attribute differ in case only {0} {1}"; ReportTrace(string.Format(CultureInfo.InvariantCulture, fmtString, a.Name, s)); return true; } @@ -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 { @@ -690,7 +685,7 @@ protected void SetLoadingInfoIsProductCode(bool isProductCode) _loadingInfo.isProductCode = isProductCode; } - private DatabaseLoadingInfo _loadingInfo = new DatabaseLoadingInfo(); + private readonly DatabaseLoadingInfo _loadingInfo = new DatabaseLoadingInfo(); protected DatabaseLoadingInfo LoadingInfo { @@ -711,13 +706,13 @@ protected DatabaseLoadingInfo LoadingInfo internal bool VerifyStringResources { get; } = true; - private int _maxNumberOfErrors = 30; + private readonly int _maxNumberOfErrors = 30; private int _currentErrorCount = 0; - private bool _logStackActivity = false; + private readonly bool _logStackActivity = false; - private Stack _executionStack = new Stack(); + private readonly Stack _executionStack = new Stack(); private XmlLoaderLogger _logger = new XmlLoaderLogger(); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs index 1402fbbbc8a..acf540246d5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/commands.cs @@ -38,4 +38,3 @@ internal static bool Convert(string expansionString, out EnumerableExpansion exp } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs index 64af2244fb8..0190a31ddc9 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData.cs @@ -102,6 +102,13 @@ internal sealed class DefaultSettingsSection { internal bool MultilineTables { + get + { + if (_multilineTables.HasValue) + return _multilineTables.Value; + return false; + } + set { if (!_multilineTables.HasValue) @@ -109,13 +116,6 @@ internal bool MultilineTables _multilineTables = value; } } - - get - { - if (_multilineTables.HasValue) - return _multilineTables.Value; - return false; - } } private bool? _multilineTables; @@ -132,6 +132,13 @@ internal sealed class FormatErrorPolicy /// internal bool ShowErrorsAsMessages { + get + { + if (_showErrorsAsMessages.HasValue) + return _showErrorsAsMessages.Value; + return false; + } + set { if (!_showErrorsAsMessages.HasValue) @@ -139,13 +146,6 @@ internal bool ShowErrorsAsMessages _showErrorsAsMessages = value; } } - - get - { - if (_showErrorsAsMessages.HasValue) - return _showErrorsAsMessages.Value; - return false; - } } private bool? _showErrorsAsMessages; @@ -156,6 +156,13 @@ internal bool ShowErrorsAsMessages /// internal bool ShowErrorsInFormattedOutput { + get + { + if (_showErrorsInFormattedOutput.HasValue) + return _showErrorsInFormattedOutput.Value; + return false; + } + set { if (!_showErrorsInFormattedOutput.HasValue) @@ -163,13 +170,6 @@ internal bool ShowErrorsInFormattedOutput _showErrorsInFormattedOutput = value; } } - - get - { - if (_showErrorsInFormattedOutput.HasValue) - return _showErrorsInFormattedOutput.Value; - return false; - } } private bool? _showErrorsInFormattedOutput; @@ -191,6 +191,13 @@ internal sealed class ShapeSelectionDirectives { internal int PropertyCountForTable { + get + { + if (_propertyCountForTable.HasValue) + return _propertyCountForTable.Value; + return 4; + } + set { if (!_propertyCountForTable.HasValue) @@ -198,13 +205,6 @@ internal int PropertyCountForTable _propertyCountForTable = value; } } - - get - { - if (_propertyCountForTable.HasValue) - return _propertyCountForTable.Value; - return 4; - } } private int? _propertyCountForTable; @@ -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 @@ -672,10 +673,10 @@ internal ExtendedTypeDefinition() public sealed class FormatViewDefinition { /// Name of the formatting view as defined in the formatting file - public string Name { get; private set; } + public string Name { get; } /// The control defined by this formatting view can be one of table, list, wide, or custom - public PSControl Control { get; private set; } + public PSControl Control { get; } /// instance id of the original view this will be used to distinguish two views with the same name and control types internal Guid InstanceId { get; set; } @@ -774,7 +775,7 @@ internal static PSControlGroupBy Get(GroupBy groupBy) return new PSControlGroupBy { Expression = new DisplayEntry(expressionToken), - Label = (groupBy.startGroup.labelTextToken != null) ? groupBy.startGroup.labelTextToken.text : null + Label = groupBy.startGroup.labelTextToken?.text }; } @@ -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 b9d1e897068..026b9b82f73 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Table.cs @@ -263,7 +263,7 @@ internal TableControl(TableControlBody tcb, ViewDefinition viewDefinition) : thi this.OutOfBand = viewDefinition.outOfBand; this.GroupBy = PSControlGroupBy.Get(viewDefinition.groupBy); - this.AutoSize = tcb.autosize.HasValue && tcb.autosize.Value; + this.AutoSize = tcb.autosize.GetValueOrDefault(); this.HideTableHeaders = tcb.header.hideHeader; TableControlRow row = new TableControlRow(tcb.defaultDefinition); @@ -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 05e3e79923b..1f5b42fd262 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayDescriptionData_Wide.cs @@ -130,7 +130,7 @@ internal WideControl(WideControlBody widecontrolbody, ViewDefinition viewDefinit OutOfBand = viewDefinition.outOfBand; GroupBy = PSControlGroupBy.Get(viewDefinition.groupBy); - AutoSize = widecontrolbody.autosize.HasValue && widecontrolbody.autosize.Value; + AutoSize = widecontrolbody.autosize.GetValueOrDefault(); Columns = (uint)widecontrolbody.columns; Entries.Add(new WideControlEntryItem(widecontrolbody.defaultEntryDefinition)); @@ -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 52e1043cb2c..b5eb4d456e8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/displayResourceManagerCache.cs @@ -13,7 +13,7 @@ internal sealed class DisplayResourceManagerCache { internal enum LoadingResult { NoError, AssemblyNotFound, ResourceNotFound, StringNotFound } - internal enum AssemblyBindingStatus { NotFound, FoundInGac, FoundInPath }; + internal enum AssemblyBindingStatus { NotFound, FoundInGac, FoundInPath } internal string GetTextTokenString(TextToken tt) { @@ -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. @@ -176,7 +176,7 @@ internal Assembly ResolveAssemblyName(string assemblyName) return retVal; } - private Assembly ResolveAssemblyNameInLoadedAssemblies(string assemblyName, bool fullName) + private static Assembly ResolveAssemblyNameInLoadedAssemblies(string assemblyName, bool fullName) { Assembly result = null; @@ -216,11 +216,10 @@ private Assembly ResolveAssemblyNameInLoadedAssemblies(string assemblyName, bool return result; } - private Hashtable _assemblyReferences = new Hashtable(StringComparer.OrdinalIgnoreCase); + private readonly Hashtable _assemblyReferences = new Hashtable(StringComparer.OrdinalIgnoreCase); } - private AssemblyNameResolver _assemblyNameResolver = new AssemblyNameResolver(); - private Hashtable _resourceReferenceToAssemblyCache = new Hashtable(); + private readonly AssemblyNameResolver _assemblyNameResolver = new AssemblyNameResolver(); + private readonly Hashtable _resourceReferenceToAssemblyCache = new Hashtable(); } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs index 985561670f8..629e419d5a2 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataManager.cs @@ -33,7 +33,7 @@ internal sealed class TypeInfoDataBaseManager internal object updateDatabaseLock = new object(); // this is used to throw errors when updating a shared TypeTable. internal bool isShared; - private List _formatFileList; + private readonly List _formatFileList; internal bool DisableFormatTableUpdates { get; set; } @@ -57,7 +57,7 @@ internal TypeInfoDataBaseManager() /// /// Host passed to . Can be null if no interactive questions should be asked. /// - /// + /// /// /// 1. FormatFile is not rooted. /// @@ -96,7 +96,7 @@ internal TypeInfoDataBaseManager( this.isShared = isShared; // check to see if there are any errors loading the format files - if (errors.Count > 0) + if (!errors.IsEmpty) { throw new FormatTableLoadException(errors); } @@ -215,7 +215,7 @@ internal void AddFormatData(IEnumerable formatData, bool LoadFromFile(filesToLoad, expressionFactory, false, null, null, false, out logEntries); // check to see if there are any errors loading the format files - if (errors.Count > 0) + if (!errors.IsEmpty) { throw new FormatTableLoadException(errors); } @@ -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 @@ -533,7 +542,7 @@ private static void ProcessBuiltinFormatViewDefinitions( } /// - /// Helper to to add any pre-load intrinsics to the db. + /// Helper to add any pre-load intrinsics to the db. /// /// Db being initialized. private static void AddPreLoadIntrinsics(TypeInfoDataBase db) @@ -542,7 +551,7 @@ private static void AddPreLoadIntrinsics(TypeInfoDataBase db) } /// - /// Helper to to add any post-load intrinsics to the db. + /// Helper to add any post-load intrinsics to the db. /// /// Db being initialized. private static void AddPostLoadIntrinsics(TypeInfoDataBase db) @@ -559,4 +568,3 @@ private static void AddPostLoadIntrinsics(TypeInfoDataBase db) } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs index ec0c40736fb..de6df333951 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); @@ -219,7 +218,7 @@ private int MatchTypeIndex(string typeName, PSObject currentObject, PSPropertyEx return BestMatchIndexUndefined; } - private bool MatchCondition(PSObject currentObject, PSPropertyExpression ex) + private static bool MatchCondition(PSObject currentObject, PSPropertyExpression ex) { if (ex == null) return true; @@ -230,10 +229,10 @@ private bool MatchCondition(PSObject currentObject, PSPropertyExpression ex) return retVal; } - private PSPropertyExpressionFactory _expressionFactory; - private TypeInfoDataBase _db; - private Collection _typeNameHierarchy; - private bool _useInheritance; + private readonly PSPropertyExpressionFactory _expressionFactory; + private readonly TypeInfoDataBase _db; + private readonly Collection _typeNameHierarchy; + private readonly bool _useInheritance; private int _bestMatchIndex = BestMatchIndexUndefined; private TypeMatchItem _bestMatchItem; @@ -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,18 +599,14 @@ 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 { // check if we have a type group reference - TypeGroupReference tgr = r as TypeGroupReference; - - if (tgr == null) + if (r is not TypeGroupReference tgr) continue; // find the type group definition the reference points to @@ -616,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 79bcbd61dca..b8a25e5e207 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataXmlLoader.cs @@ -43,7 +43,7 @@ internal sealed partial class TypeInfoDataBaseLoader : XmlLoaderBase #region tracer [TraceSource("TypeInfoDataBaseLoader", "TypeInfoDataBaseLoader")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("TypeInfoDataBaseLoader", "TypeInfoDataBaseLoader"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("TypeInfoDataBaseLoader", "TypeInfoDataBaseLoader"); #endregion tracer /// @@ -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); @@ -586,7 +589,7 @@ private ControlBase LoadTableControlFromObjectModel(TableControl table, int view /// /// /// - private void LoadHeadersSectionFromObjectModel(TableControlBody tableBody, List headers) + private static void LoadHeadersSectionFromObjectModel(TableControlBody tableBody, List headers) { foreach (TableControlColumnHeader header in headers) { @@ -746,7 +749,7 @@ private ExpressionToken LoadExpressionFromObjectModel(DisplayEntry displayEntry, /// Load EntrySelectedBy (TypeName) into AppliesTo. /// /// - private AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, List condition) + private static AppliesTo LoadAppliesToSectionFromObjectModel(List selectedBy, List condition) { AppliesTo appliesTo = new AppliesTo(); @@ -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 }; @@ -1122,9 +1122,9 @@ private FormatToken LoadFormatTokenFromObjectModel(CustomItemBase item, int view { frameInfoDefinition = { - leftIndentation = (int) frame.LeftIndent, - rightIndentation = (int) frame.RightIndent, - firstLine = frame.FirstLineHanging != 0 ? -(int) frame.FirstLineHanging : (int) frame.FirstLineIndent + leftIndentation = (int)frame.LeftIndent, + rightIndentation = (int)frame.RightIndent, + firstLine = frame.FirstLineHanging != 0 ? -(int)frame.FirstLineHanging : (int)frame.FirstLineIndent } }; @@ -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)); @@ -2035,7 +2034,7 @@ internal ExpressionToken GenerateExpressionToken() return _token; } - private TypeInfoDataBaseLoader _loader; + private readonly TypeInfoDataBaseLoader _loader; private ExpressionToken _token; private bool _fatalError = false; } @@ -2175,7 +2174,7 @@ internal bool ProcessExpressionDirectives(XmlNode containerNode, List u private TextToken _textToken; private ExpressionToken _expression; - private TypeInfoDataBaseLoader _loader; + private readonly TypeInfoDataBaseLoader _loader; } #endregion @@ -2228,10 +2227,9 @@ internal ControlBase Control } private ControlBase _control; - private TypeInfoDataBaseLoader _loader; + private readonly TypeInfoDataBaseLoader _loader; } #endregion } } - 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 8b960e32297..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); } @@ -170,7 +172,7 @@ private bool LoadMainControlDependentData(List unprocessedNodes, ViewDe return false; } - if (!(view.mainControl is ComplexControlBody) && !(view.mainControl is ListControlBody)) + if (view.mainControl is not ComplexControlBody && view.mainControl is not ListControlBody) { // Error at XPath {0} in file {1}: Out Of Band views can only have CustomControl or ListControl. ReportError(StringUtil.Format(FormatAndOutXmlLoadingStrings.InvalidControlForOutOfBandView, ComputeCurrentXPath(), FilePath)); diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs index c3d733a7f2d..a4e2a74f622 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatGroupManager.cs @@ -121,4 +121,3 @@ private static bool IsEqual(object first, object second) private object _currentGroupingKeyPropertyValue = AutomationNull.Value; } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs index 0aaf16a9a28..1018936d2c1 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatMsgCtxManager.cs @@ -19,10 +19,15 @@ internal class FormatMessagesContextManager { // callbacks declarations internal delegate OutputContext FormatContextCreationCallback(OutputContext parentContext, FormatInfoData formatData); + internal delegate void FormatStartCallback(OutputContext c); + internal delegate void FormatEndCallback(FormatEndData fe, OutputContext c); + internal delegate void GroupStartCallback(OutputContext c); + internal delegate void GroupEndCallback(GroupEndData fe, OutputContext c); + internal delegate void PayloadCallback(FormatEntryData formatEntryData, OutputContext c); // callback instances @@ -63,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; @@ -135,6 +139,6 @@ internal OutputContext ActiveOutputContext /// /// Internal stack to manage context. /// - private Stack _stack = new Stack(); + private readonly Stack _stack = new Stack(); } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index cf6a4d2eb88..963b5a0f88b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Linq; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Internal; @@ -141,13 +142,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 +217,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) @@ -307,7 +305,7 @@ internal bool IsObjectApplicable(Collection typeNames) // we were unable to find a best match so far..try // to get rid of Deserialization prefix and see if a // match can be found. - if (false == result) + if (!result) { Collection typesWithoutPrefix = Deserializer.MaskDeserializationPrefix(typeNames); if (typesWithoutPrefix != null) @@ -350,8 +348,50 @@ protected class DataBaseInfo protected DataBaseInfo dataBaseInfo = new DataBaseInfo(); - protected List activeAssociationList = null; - protected FormattingCommandLineParameters inputParameters = null; + /// + /// Builds the raw association list for the given object. + /// Subclasses override this to provide cmdlet-specific property expansion logic. + /// + /// The object to build the association list for. + /// The list of properties specified by the user, or null if not specified. + /// The raw association list, or null if not applicable. + protected virtual List BuildRawAssociationList(PSObject so, List propertyList) + { + return null; + } + + /// + /// Builds the active association list for the given object, with ExcludeProperty filter applied. + /// + /// The object to build the association list for. + /// The filtered association list. + protected List BuildActiveAssociationList(PSObject so) + { + var propertyList = parameters?.mshParameterList; + var excludeFilter = parameters?.excludePropertyFilter; + var rawList = BuildRawAssociationList(so, propertyList); + return ApplyExcludeFilter(rawList, excludeFilter); + } + + /// + /// Applies the ExcludeProperty filter to the given association list. + /// + /// The list to filter. + /// The exclude filter to apply. + /// The filtered list, or the original list if no filter is specified. + internal static List ApplyExcludeFilter( + List associationList, + PSPropertyExpressionFilter excludeFilter) + { + if (associationList is null || excludeFilter is null) + { + return associationList; + } + + return associationList + .Where(item => !excludeFilter.IsMatch(item.ResolvedExpression)) + .ToList(); + } protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, FieldFormattingDirective directive) @@ -387,7 +427,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 +479,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 c9cd80bbee8..b81c0c0f860 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -16,7 +16,6 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; } internal override FormatStartData GenerateStartData(PSObject so) @@ -40,7 +39,7 @@ internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLi private ComplexViewEntry GenerateComplexViewEntryFromProperties(PSObject so, int enumerationLimit) { ComplexViewObjectBrowser browser = new ComplexViewObjectBrowser(this.ErrorManager, this.expressionFactory, enumerationLimit); - return browser.GenerateView(so, this.inputParameters); + return browser.GenerateView(so, this.parameters); } private ComplexViewEntry GenerateComplexViewEntryFromDataBaseInfo(PSObject so, int enumerationLimit) @@ -107,8 +106,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 +203,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 +211,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 +221,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 +240,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 +277,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; @@ -383,13 +374,13 @@ private bool EvaluateDisplayCondition(PSObject so, ExpressionToken conditionToke return retVal; } - private TypeInfoDataBase _db; - private DatabaseLoadingInfo _loadingInfo; - private PSPropertyExpressionFactory _expressionFactory; - private List _controlDefinitionList; - private FormatErrorManager _errorManager; - private TerminatingErrorContext _errorContext; - private int _enumerationLimit; + private readonly TypeInfoDataBase _db; + private readonly DatabaseLoadingInfo _loadingInfo; + private readonly PSPropertyExpressionFactory _expressionFactory; + private readonly List _controlDefinitionList; + private readonly FormatErrorManager _errorManager; + private readonly TerminatingErrorContext _errorContext; + private readonly int _enumerationLimit; } internal class TraversalInfo @@ -412,8 +403,8 @@ internal TraversalInfo NextLevel } } - private int _level; - private int _maxDepth; + private readonly int _level; + private readonly int _maxDepth; } /// @@ -433,17 +424,18 @@ internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, PSPrope /// of the object. /// /// Object to process. - /// Parameters from the command line. + /// Parameters from the command line. /// Complex view entry to send to the output command. - internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters inputParameters) + internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters parameters) { - _complexSpecificParameters = (ComplexSpecificParameters)inputParameters.shapeParameters; + _parameters = parameters; + _complexSpecificParameters = (ComplexSpecificParameters)parameters.shapeParameters; int maxDepth = _complexSpecificParameters.maxDepth; TraversalInfo level = new TraversalInfo(0, maxDepth); List mshParameterList = null; - mshParameterList = inputParameters.mshParameterList; + mshParameterList = parameters.mshParameterList; // create a top level entry as root of the tree ComplexViewEntry cve = new ComplexViewEntry(); @@ -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) @@ -521,6 +513,9 @@ private void DisplayObject(PSObject so, TraversalInfo currentLevel, List activeAssociationList = AssociationManager.SetupActiveProperties(parameterList, so, _expressionFactory); + // Apply ExcludeProperty filter using the centralized method + activeAssociationList = ViewGenerator.ApplyExcludeFilter(activeAssociationList, _parameters?.excludePropertyFilter); + // create a format entry FormatEntry fe = new FormatEntry(); formatValueList.Add(fe); @@ -717,7 +712,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]; } @@ -766,18 +761,18 @@ private List AddIndentationLevel(List formatValueList) return feFrame.formatValueList; } + private FormattingCommandLineParameters _parameters; private ComplexSpecificParameters _complexSpecificParameters; /// /// Indentation added to each level in the recursion. /// - private int _indentationStep = 2; + private readonly int _indentationStep = 2; - private FormatErrorManager _errorManager; + private readonly FormatErrorManager _errorManager; - private PSPropertyExpressionFactory _expressionFactory; + private readonly PSPropertyExpressionFactory _expressionFactory; - private int _enumerationLimit; + private readonly int _enumerationLimit; } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs index 2ded68cd510..35287d7c2e4 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -30,9 +30,14 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper { _listBody = (ListControlBody)this.dataBaseInfo.view.mainControl; } + } - this.inputParameters = parameters; - SetUpActiveProperties(so); + /// + /// Builds the raw association list for list formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { + return AssociationManager.SetupActiveProperties(propertyList, so, this.expressionFactory); } /// @@ -114,20 +119,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); } } @@ -181,17 +183,14 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperties(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); ListViewEntry lve = new ListViewEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < associationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = associationList[k]; ListViewField lvf = new ListViewField(); if (a.OriginatingParameter != null) @@ -221,20 +220,7 @@ private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enume lvf.formatPropertyField.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); lve.listViewFieldList.Add(lvf); } - - this.activeAssociationList = null; return lve; } - - private void SetUpActiveProperties(PSObject so) - { - List mshParameterList = null; - - if (this.inputParameters != null) - mshParameterList = this.inputParameters.mshParameterList; - - this.activeAssociationList = AssociationManager.SetupActiveProperties(mshParameterList, so, this.expressionFactory); - } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs index ab4912c8838..3b14c0754ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -14,6 +14,8 @@ internal sealed class TableViewGenerator : ViewGenerator // tableBody to use for this instance of the ViewGenerator; private TableControlBody _tableBody; + private List _activeAssociationList; + internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) { base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters); @@ -34,46 +36,48 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper _tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl; } - List rawMshParameterList = null; - - if (parameters != null) - rawMshParameterList = parameters.mshParameterList; + // Build the active association list (with ExcludeProperty filter applied) + _activeAssociationList = BuildActiveAssociationList(so); + } + /// + /// Builds the raw association list for table formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) + if (propertyList is not null && propertyList.Count > 0) { - this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so); - return; + return AssociationManager.ExpandTableParameters(propertyList, so); } // we did not get any properties: // try to get properties from the default property set of the object - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count > 0) { // we got a valid set of properties from the default property set..add computername for // remoteobjects (if available) if (PSObjectHelper.ShouldShowComputerNameProperty(so)) { - activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, + list.Add(new MshResolvedExpressionParameterAssociation(null, new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); } - return; + return list; } // we failed to get anything from the default property set - this.activeAssociationList = AssociationManager.ExpandAll(so); - if (this.activeAssociationList.Count > 0) + list = AssociationManager.ExpandAll(so); + if (list.Count > 0) { // Remove PSComputerName and PSShowComputerName from the display as needed. - AssociationManager.HandleComputerNameProperties(so, activeAssociationList); - FilterActiveAssociationList(); - return; + AssociationManager.HandleComputerNameProperties(so, list); + return LimitAssociationListSize(list); } // we were unable to retrieve any properties, so we leave an empty list - this.activeAssociationList = new List(); + return new List(); } /// @@ -124,30 +128,29 @@ internal override FormatStartData GenerateStartData(PSObject so) } /// - /// Method to filter resolved expressions as per table view needs. + /// Limits the association list size for table view. /// For v1.0, table view supports only 10 properties. - /// - /// This method filters and updates "activeAssociationList" instance property. /// - /// None. - /// This method updates "activeAssociationList" instance property. - private void FilterActiveAssociationList() + /// The list to limit. + /// The limited list. + private static List LimitAssociationListSize( + List list) { - // we got a valid set of properties from the default property set - // make sure we do not have too many properties - // NOTE: this is an arbitrary number, chosen to be a sensitive default - int nMax = 10; + const int maxCount = 10; - if (activeAssociationList.Count > nMax) + if (list.Count <= maxCount) { - List tmp = this.activeAssociationList; - this.activeAssociationList = new List(); - for (int k = 0; k < nMax; k++) - this.activeAssociationList.Add(tmp[k]); + return list; } - return; + var result = new List(maxCount); + for (int k = 0; k < maxCount; k++) + { + result.Add(list[k]); + } + + return result; } private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) @@ -172,7 +175,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 +197,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,10 +224,11 @@ 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++) + for (int k = 0; k < _activeAssociationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = _activeAssociationList[k]; TableColumnInfo ci = new TableColumnInfo(); // set the label of the column @@ -233,10 +239,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) ci.propertyName = (string)key; } - if (ci.propertyName == null) - { - ci.propertyName = this.activeAssociationList[k].ResolvedExpression.ToString(); - } + ci.propertyName ??= _activeAssociationList[k].ResolvedExpression.ToString(); // set the width of the table if (a.OriginatingParameter != null) @@ -391,10 +394,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; if (matchingRowDefinition == null) { @@ -412,10 +412,7 @@ private List GetActiveTableRowDefinition(TableControlBod } } - if (matchingRowDefinition == null) - { - matchingRowDefinition = match.BestMatch as TableRowDefinition; - } + matchingRowDefinition ??= match.BestMatch as TableRowDefinition; } } @@ -474,16 +471,22 @@ private TableRowEntry GenerateTableRowEntryFromDataBaseInfo(PSObject so, int enu private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int enumerationLimit) { TableRowEntry tre = new TableRowEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { FormatPropertyField fpf = new FormatPropertyField(); FieldFormattingDirective directive = null; - if (activeAssociationList[k].OriginatingParameter != null) + if (_activeAssociationList[k].OriginatingParameter != null) + { + directive = _activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; + } + + if (directive is null) { - directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; + directive = new FieldFormattingDirective(); + directive.isTable = true; } - fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive); + fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, _activeAssociationList[k].ResolvedExpression, directive); tre.formatPropertyFieldList.Add(fpf); } @@ -491,4 +494,3 @@ private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int e } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs index 9d378eaf9cc..bfa364cc450 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs @@ -13,7 +13,40 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; + } + + /// + /// Builds the raw association list for wide formatting. + /// + protected override List BuildRawAssociationList(PSObject so, List propertyList) + { + // check if we received properties from the command line + if (propertyList is not null && propertyList.Count > 0) + { + return AssociationManager.ExpandParameters(propertyList, so); + } + + // we did not get any properties: + // try to get the display property of the object + PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); + if (displayNameExpression is not null) + { + return new List + { + new MshResolvedExpressionParameterAssociation(null, displayNameExpression) + }; + } + + // try to get the default property set (we will use the first property) + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count == 0) + { + // we failed to get anything from the default property set + // just get all the properties + list = AssociationManager.ExpandAll(so); + } + + return list; } internal override FormatStartData GenerateStartData(PSObject so) @@ -130,20 +163,17 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperty(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); WideViewEntry wve = new WideViewEntry(); FormatPropertyField fpf = new FormatPropertyField(); wve.formatPropertyField = fpf; - if (this.activeAssociationList.Count > 0) + if (associationList.Count > 0) { // get the first one - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[0]; + MshResolvedExpressionParameterAssociation a = associationList[0]; FieldFormattingDirective directive = null; if (a.OriginatingParameter != null) { @@ -152,47 +182,7 @@ private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enume fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); } - - this.activeAssociationList = null; return wve; } - - private void SetUpActiveProperty(PSObject so) - { - List rawMshParameterList = null; - - if (this.inputParameters != null) - rawMshParameterList = this.inputParameters.mshParameterList; - - // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) - { - this.activeAssociationList = AssociationManager.ExpandParameters(rawMshParameterList, so); - return; - } - - // we did not get any properties: - // try to get the display property of the object - PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); - if (displayNameExpression != null) - { - this.activeAssociationList = new List(); - this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression)); - return; - } - - // try to get the default property set (we will use the first property) - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) - { - // we got a valid set of properties from the default property set - return; - } - - // we failed to get anything from the default property set - // just get all the properties - this.activeAssociationList = AssociationManager.ExpandAll(so); - } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs index aca38f8994b..891dfce6829 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewManager.cs @@ -70,7 +70,7 @@ internal sealed class FormatViewManager { #region tracer [TraceSource("FormatViewBinding", "Format view binding")] - private static PSTraceSource s_formatViewBindingTracer = PSTraceSource.GetTracer("FormatViewBinding", "Format view binding", false); + private static readonly PSTraceSource s_formatViewBindingTracer = PSTraceSource.GetTracer("FormatViewBinding", "Format view binding", false); #endregion tracer private static string PSObjectTypeName(PSObject so) @@ -234,7 +234,7 @@ private static void ProcessUnknownViewName(TerminatingErrorContext errorContext, string msg = null; bool foundValidViews = false; string formatTypeName = null; - string separator = ", "; + const string separator = ", "; StringBuilder validViewFormats = new StringBuilder(); if (so != null && so.BaseObject != null && @@ -336,7 +336,7 @@ private static void ProcessUnknownViewName(TerminatingErrorContext errorContext, { // unKnowViewFormatStringBuilder.Append(StringUtil.Format(FormatAndOut_format_xxx.UnknownViewNameError, viewName)); unKnowViewFormatStringBuilder.Append(StringUtil.Format(FormatAndOut_format_xxx.UnknownViewNameErrorSuffix, viewName, formatTypeName)); - unKnowViewFormatStringBuilder.Append(validViewFormats.ToString()); + unKnowViewFormatStringBuilder.Append(validViewFormats); } else { @@ -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, @@ -677,12 +675,11 @@ private static ErrorRecord GenerateErrorRecord(FormattingError error) return errorRecord; } - private FormatErrorPolicy _formatErrorPolicy; + private readonly FormatErrorPolicy _formatErrorPolicy; /// /// Current list of failed PSPropertyExpression evaluations. /// - private List _formattingErrorList = new List(); + private readonly List _formattingErrorList = new List(); } } - 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 21630c1a9c1..7ae144702ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// internal sealed class FormatObjectDeserializer { - internal TerminatingErrorContext TerminatingErrorContext { get; private set; } + internal TerminatingErrorContext TerminatingErrorContext { get; } /// /// Expansion of TAB character to the following string. @@ -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 || @@ -55,9 +54,7 @@ fid is GroupEndData || return false; } - string classId = GetProperty(so, FormatInfoData.classidProperty) as string; - - if (classId == null) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData return false; @@ -88,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 || @@ -113,9 +109,7 @@ fid is GroupEndData || return so; } - string classId = GetProperty(so, FormatInfoData.classidProperty) as string; - - if (classId == null) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData, // just return it as is @@ -294,7 +288,7 @@ internal int DeserializeIntMemberVariable(PSObject so, string property) internal bool DeserializeBoolMemberVariable(PSObject so, string property, bool cannotBeNull = true) { var val = DeserializeMemberVariable(so, property, typeof(bool), cannotBeNull); - return (val == null) ? false : (bool)val; + return val != null && (bool)val; } internal WriteStreamType DeserializeWriteStreamTypeMemberVariable(PSObject so) @@ -329,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; } @@ -359,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()} }; } @@ -706,4 +698,3 @@ internal override void Deserialize(PSObject so, FormatObjectDeserializer deseria } #endregion } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs index 09432253e33..7bca25ea828 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ILineOutput.cs @@ -1,9 +1,12 @@ // 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; // interfaces for host interaction @@ -12,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 @@ -82,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); @@ -92,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]); @@ -119,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++; @@ -130,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 } /// @@ -193,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; @@ -258,17 +322,17 @@ internal class WriteLineHelper /// Instance of the delegate previously defined /// for line that has EXACTLY this.ncols characters. /// - private WriteCallback _writeCall = null; + private readonly WriteCallback _writeCall = null; /// /// Instance of the delegate previously defined /// for generic line, less that this.ncols characters. /// - private WriteCallback _writeLineCall = null; + private readonly WriteCallback _writeLineCall = null; #endregion - private bool _lineWrap; + private readonly bool _lineWrap; /// /// Construct an instance, given the two callbacks @@ -323,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]); @@ -352,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 @@ -367,14 +431,14 @@ private void WriteLineInternal(string val, int cols) } } - private DisplayCells _displayCells; + private readonly DisplayCells _displayCells; } /// /// 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 @@ -409,8 +473,19 @@ internal override int RowNumber /// /// internal override void WriteLine(string s) + { + WriteRawText(PSHostUserInterface.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) { _writer.Write(s); @@ -420,6 +495,7 @@ internal override void WriteLine(string s) _writer.WriteLine(s); } } + #endregion /// @@ -447,11 +523,11 @@ internal TextWriterLineOutput(TextWriter writer, int columns, bool suppressNewli _suppressNewline = suppressNewline; } - private int _columns = 0; + private readonly int _columns = 0; - private TextWriter _writer = null; + private readonly TextWriter _writer = null; - private bool _suppressNewline = false; + private readonly bool _suppressNewline = false; } /// @@ -462,7 +538,7 @@ internal class StreamingTextWriter : TextWriter { #region tracer [TraceSource("StreamingTextWriter", "StreamingTextWriter")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("StreamingTextWriter", "StreamingTextWriter"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("StreamingTextWriter", "StreamingTextWriter"); #endregion tracer /// @@ -499,6 +575,6 @@ public override void WriteLine(string s) /// /// Instance of the delegate previously defined. /// - private WriteLineCallback _writeCall = null; + private readonly WriteLineCallback _writeCall = null; } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs index 91a7b8698a6..988b1133748 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.AsSpan().TrimEnd().EndsWith(reset, StringComparison.Ordinal)) + { + _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 b07731c3e04..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; } } @@ -135,7 +130,7 @@ protected override void InternalDispose() /// /// Lock object. /// - private object _syncRoot = new object(); + private readonly object _syncRoot = new object(); } /// @@ -185,7 +180,7 @@ public void Dispose() /// /// Ordered list of ETS type names this object is handling. /// - private StringCollection _applicableTypes = new StringCollection(); + private readonly StringCollection _applicableTypes = new StringCollection(); } /// @@ -322,7 +317,7 @@ private CommandEntry GetActiveCommandEntry(PSObject so) /// /// List of command entries, each with a set of applicable types. /// - private List _commandEntryList = new List(); + private readonly List _commandEntryList = new List(); /// /// Default command entry to be executed when all type matches fail. diff --git a/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs b/src/System.Management.Automation/FormatAndOutput/common/OutputQueue.cs index 1545f456e12..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); @@ -164,28 +160,28 @@ internal PacketInfoData Dequeue() /// /// Queue to store the currently cached objects. /// - private Queue _queue = new Queue(); + private readonly Queue _queue = new Queue(); /// /// Number of objects to compute the best fit. /// Zero: all the objects /// a positive number N: use the first N. /// - private int _objectCount = 0; + private readonly int _objectCount = 0; /// /// Maximum amount of time for record processing to compute the best fit. /// MaxValue: all the objects. /// A positive timespan: use all objects that have been processed within the timeframe. /// - private TimeSpan _groupingDuration = TimeSpan.MinValue; + private readonly TimeSpan _groupingDuration = TimeSpan.MinValue; private Stopwatch _groupingTimer = null; /// /// Notification callback to be called when we have accumulated enough /// data to compute a hint. /// - private FormattedObjectsCache.ProcessCachedGroupNotification _notificationCallBack = null; + private readonly FormattedObjectsCache.ProcessCachedGroupNotification _notificationCallBack = null; /// /// Reference kept to be used during notification. @@ -327,7 +323,7 @@ internal List Drain() /// /// Front end queue (if present, cache ALL, if not, bypass) /// - private Queue _frontEndQueue; + private readonly Queue _frontEndQueue; /// /// Back end grouping queue. @@ -335,4 +331,3 @@ internal List Drain() private OutputGroupQueue _groupQueue = null; } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs new file mode 100644 index 00000000000..534f9541195 --- /dev/null +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -0,0 +1,911 @@ +// 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 + /// + /// Defines the options for output rendering. + /// + public enum OutputRendering + { + /// Render ANSI only to host. + Host = 0, + + /// Render as plaintext. + PlainText = 1, + + /// Render as ANSI. + Ansi = 2, + } + #endregion OutputRendering + + /// + /// Defines the options for views of progress rendering. + /// + public enum ProgressView + { + /// Render progress using minimal space. + Minimal = 0, + + /// Classic rendering of progress. + Classic = 1, + } + + #region PSStyle + /// + /// Contains configuration for how PowerShell renders text. + /// + public sealed class PSStyle + { + /// + /// Contains foreground colors. + /// + public sealed class ForegroundColor + { + /// + /// Gets the color black. + /// + public string Black { get; } = "\x1b[30m"; + + /// + /// Gets the color red. + /// + public string Red { get; } = "\x1b[31m"; + + /// + /// Gets the color green. + /// + public string Green { get; } = "\x1b[32m"; + + /// + /// Gets the color yellow. + /// + public string Yellow { get; } = "\x1b[33m"; + + /// + /// Gets the color blue. + /// + public string Blue { get; } = "\x1b[34m"; + + /// + /// Gets the color magenta. + /// + public string Magenta { get; } = "\x1b[35m"; + + /// + /// Gets the color cyan. + /// + public string Cyan { get; } = "\x1b[36m"; + + /// + /// Gets the color white. + /// + public string White { get; } = "\x1b[37m"; + + /// + /// Gets the color bright black. + /// + public string BrightBlack { get; } = "\x1b[90m"; + + /// + /// Gets the color bright red. + /// + public string BrightRed { get; } = "\x1b[91m"; + + /// + /// Gets the color bright green. + /// + public string BrightGreen { get; } = "\x1b[92m"; + + /// + /// Gets the color bright yellow. + /// + public string BrightYellow { get; } = "\x1b[93m"; + + /// + /// Gets the color bright blue. + /// + public string BrightBlue { get; } = "\x1b[94m"; + + /// + /// Gets the color bright magenta. + /// + public string BrightMagenta { get; } = "\x1b[95m"; + + /// + /// Gets the color bright cyan. + /// + public string BrightCyan { get; } = "\x1b[96m"; + + /// + /// Gets the color bright white. + /// + public string BrightWhite { get; } = "\x1b[97m"; + + /// + /// Set as RGB (Red, Green, Blue). + /// + /// Byte value representing red. + /// Byte value representing green. + /// Byte value representing blue. + /// String representing ANSI code for RGB value. + public string FromRgb(byte red, byte green, byte blue) + { + return $"\x1b[38;2;{red};{green};{blue}m"; + } + + /// + /// The color set as RGB as a single number. + /// + /// RGB value specified as an integer. + /// String representing ANSI code for RGB value. + public string FromRgb(int rgb) + { + byte red, green, blue; + blue = (byte)(rgb & 0xFF); + rgb >>= 8; + green = (byte)(rgb & 0xFF); + rgb >>= 8; + red = (byte)(rgb & 0xFF); + + 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); + } + } + + /// + /// Contains background colors. + /// + public sealed class BackgroundColor + { + /// + /// Gets the color black. + /// + public string Black { get; } = "\x1b[40m"; + + /// + /// Gets the color red. + /// + public string Red { get; } = "\x1b[41m"; + + /// + /// Gets the color green. + /// + public string Green { get; } = "\x1b[42m"; + + /// + /// Gets the color yellow. + /// + public string Yellow { get; } = "\x1b[43m"; + + /// + /// Gets the color blue. + /// + public string Blue { get; } = "\x1b[44m"; + + /// + /// Gets the color magenta. + /// + public string Magenta { get; } = "\x1b[45m"; + + /// + /// Gets the color cyan. + /// + public string Cyan { get; } = "\x1b[46m"; + + /// + /// Gets the color white. + /// + public string White { get; } = "\x1b[47m"; + + /// + /// Gets the color bright black. + /// + public string BrightBlack { get; } = "\x1b[100m"; + + /// + /// Gets the color bright red. + /// + public string BrightRed { get; } = "\x1b[101m"; + + /// + /// Gets the color bright green. + /// + public string BrightGreen { get; } = "\x1b[102m"; + + /// + /// Gets the color bright yellow. + /// + public string BrightYellow { get; } = "\x1b[103m"; + + /// + /// Gets the color bright blue. + /// + public string BrightBlue { get; } = "\x1b[104m"; + + /// + /// Gets the color bright magenta. + /// + public string BrightMagenta { get; } = "\x1b[105m"; + + /// + /// Gets the color bright cyan. + /// + public string BrightCyan { get; } = "\x1b[106m"; + + /// + /// Gets the color bright white. + /// + public string BrightWhite { get; } = "\x1b[107m"; + + /// + /// The color set as RGB (Red, Green, Blue). + /// + /// Byte value representing red. + /// Byte value representing green. + /// Byte value representing blue. + /// String representing ANSI code for RGB value. + public string FromRgb(byte red, byte green, byte blue) + { + return $"\x1b[48;2;{red};{green};{blue}m"; + } + + /// + /// The color set as RGB as a single number. + /// + /// RGB value specified as an integer. + /// String representing ANSI code for RGB value. + public string FromRgb(int rgb) + { + byte red, green, blue; + blue = (byte)(rgb & 0xFF); + rgb >>= 8; + green = (byte)(rgb & 0xFF); + rgb >>= 8; + red = (byte)(rgb & 0xFF); + + 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); + } + } + + /// + /// Contains configuration for the progress bar visualization. + /// + public sealed class ProgressConfiguration + { + /// + /// Gets or sets the style for progress bar. + /// + 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 => _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. + /// + public ProgressView View { get; set; } = ProgressView.Minimal; + + /// + /// Gets or sets a value indicating whether to use Operating System Command (OSC) control sequences 'ESC ]9;4;' to show indicator in terminal. + /// + public bool UseOSCIndicator { get; set; } = false; + } + + /// + /// Contains formatting styles for steams and objects. + /// + public sealed class FormattingData + { + /// + /// Gets or sets the accent style for formatting. + /// + 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 => _errorAccent; + set => _errorAccent = ValidateNoContent(value); + } + + private string _errorAccent = "\x1b[36;1m"; + + /// + /// Gets or sets the style for error messages. + /// + 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 => _warning; + set => _warning = ValidateNoContent(value); + } + + private string _warning = "\x1b[33;1m"; + + /// + /// Gets or sets the style for verbose messages. + /// + 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 => _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.Host; + + /// + /// Gets value to turn off all attributes. + /// + public string Reset { get; } = "\x1b[0m"; + + /// + /// Gets value to turn off blink. + /// + public string BlinkOff { get; } = "\x1b[25m"; + + /// + /// Gets value to turn on blink. + /// + public string Blink { get; } = "\x1b[5m"; + + /// + /// Gets value to turn off bold. + /// + public string BoldOff { get; } = "\x1b[22m"; + + /// + /// Gets value to turn on blink. + /// + 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. + /// + public string Hidden { get; } = "\x1b[8m"; + + /// + /// Gets value to turn off hidden. + /// + public string HiddenOff { get; } = "\x1b[28m"; + + /// + /// Gets value to turn on reverse. + /// + public string Reverse { get; } = "\x1b[7m"; + + /// + /// Gets value to turn off reverse. + /// + public string ReverseOff { get; } = "\x1b[27m"; + + /// + /// Gets value to turn off standout. + /// + public string ItalicOff { get; } = "\x1b[23m"; + + /// + /// Gets value to turn on standout. + /// + public string Italic { get; } = "\x1b[3m"; + + /// + /// Gets value to turn off underlined. + /// + public string UnderlineOff { get; } = "\x1b[24m"; + + /// + /// Gets value to turn on underlined. + /// + public string Underline { get; } = "\x1b[4m"; + + /// + /// Gets value to turn off strikethrough. + /// + public string StrikethroughOff { get; } = "\x1b[29m"; + + /// + /// Gets value to turn on strikethrough. + /// + public string Strikethrough { get; } = "\x1b[9m"; + + /// + /// Gets ANSI representation of a hyperlink. + /// + /// Text describing the link. + /// A valid hyperlink. + /// String representing ANSI code for the hyperlink. + public string FormatHyperlink(string text, Uri link) + { + return $"\x1b]8;;{link}\x1b\\{text}\x1b]8;;\x1b\\"; + } + + /// + /// Gets the formatting rendering settings. + /// + public FormattingData Formatting { get; } + + /// + /// Gets the configuration for progress rendering. + /// + public ProgressConfiguration Progress { get; } + + /// + /// Gets foreground colors. + /// + public ForegroundColor Foreground { get; } + + /// + /// Gets background colors. + /// + public BackgroundColor Background { get; } + + /// + /// Gets FileInfo colors. + /// + public FileInfoFormatting FileInfo { get; } + + private static readonly PSStyle s_psstyle = new PSStyle(); + + private PSStyle() + { + Formatting = new FormattingData(); + 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; + } + + /// + /// Gets singleton instance. + /// + public static PSStyle Instance + { + get + { + 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 >= BackgroundColorMap.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 new file mode 100644 index 00000000000..c76d5ebc50e --- /dev/null +++ b/src/System.Management.Automation/FormatAndOutput/common/StringDecorated.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace System.Management.Automation.Internal +{ + /// + /// Extensions to String type to calculate and render decorated content. + /// + public class StringDecorated + { + private readonly bool _isDecorated; + private readonly string _text; + private string? _plaintextcontent; + + private string PlainText + { + get + { + _plaintextcontent ??= ValueStringDecorated.AnsiRegex.Replace(_text, string.Empty); + + return _plaintextcontent; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The input string. + public StringDecorated(string text) + { + _text = text; + _isDecorated = text.Contains(ValueStringDecorated.ESC); + } + + /// + /// Gets a value indicating whether the string contains decoration. + /// + /// Boolean if the string contains decoration. + public bool IsDecorated => _isDecorated; + + /// + /// Gets the length of content sans escape sequences. + /// + /// Length of content sans escape sequences. + public int ContentLength => PlainText.Length; + + /// + /// Render the decorarted string using automatic output rendering. + /// + /// Rendered string based on automatic output rendering. + public override string ToString() => ToString( + PSStyle.Instance.OutputRendering == OutputRendering.PlainText + ? OutputRendering.PlainText + : OutputRendering.Ansi); + + /// + /// Return string representation of content depending on output rendering mode. + /// + /// Specify how to render the text content. + /// Rendered string based on outputRendering. + public string ToString(OutputRendering outputRendering) + { + if (outputRendering == OutputRendering.Host) + { + throw new ArgumentException(StringDecoratedStrings.RequireExplicitRendering); + } + + if (!_isDecorated) + { + return _text; + } + + return outputRendering == OutputRendering.PlainText ? PlainText : _text; + } + } + + internal struct ValueStringDecorated + { + internal const char ESC = '\x1b'; + private readonly bool _isDecorated; + private readonly string _text; + private string? _plaintextcontent; + private Dictionary? _vtRanges; + + private string PlainText + { + get + { + _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($"{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. + /// + /// The input string. + public ValueStringDecorated(string text) + { + _text = text; + _isDecorated = text.Contains(ESC); + _plaintextcontent = _isDecorated ? null : text; + _vtRanges = null; + } + + /// + /// Gets a value indicating whether the string contains decoration. + /// + /// Boolean if the string contains decoration. + public bool IsDecorated => _isDecorated; + + /// + /// Gets the length of content sans escape sequences. + /// + /// Length of content sans escape sequences. + public int ContentLength => PlainText.Length; + + /// + /// Render the decorarted string using automatic output rendering. + /// + /// Rendered string based on automatic output rendering. + public override string ToString() => ToString( + PSStyle.Instance.OutputRendering == OutputRendering.PlainText + ? OutputRendering.PlainText + : OutputRendering.Ansi); + + /// + /// Return string representation of content depending on output rendering mode. + /// + /// Specify how to render the text content. + /// Rendered string based on outputRendering. + public string ToString(OutputRendering outputRendering) + { + if (outputRendering == OutputRendering.Host) + { + throw new ArgumentException(StringDecoratedStrings.RequireExplicitRendering); + } + + 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 e01be6f5b8c..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,20 +199,22 @@ 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; Span currentAlignment = cols <= OutCommandInner.StackAllocThreshold ? stackalloc int[cols] : new int[cols]; - if (alignment == null) + if (alignment.IsEmpty) { for (int i = 0; i < currentAlignment.Length; i++) { @@ -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 c890599cb1e..ea200bcb5f5 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshObjectUtil.cs @@ -7,6 +7,7 @@ using System.Collections.ObjectModel; using System.Globalization; using System.Management.Automation; +using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; using System.Reflection; using System.Text; @@ -19,7 +20,13 @@ namespace Microsoft.PowerShell.Commands.Internal.Format /// internal static class PSObjectHelper { + #region tracer + [TraceSource("PSObjectHelper", "PSObjectHelper")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("PSObjectHelper", "PSObjectHelper"); + #endregion tracer + internal const char Ellipsis = '\u2026'; + internal const string EllipsisStr = "\u2026"; internal static string PSObjectIsOfExactType(Collection typeNames) { @@ -114,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; } @@ -201,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; @@ -213,15 +220,14 @@ internal static string SmartToString(PSObject so, PSPropertyExpressionFactory ex if (e != null) { StringBuilder sb = new StringBuilder(); - sb.Append("{"); + sb.Append('{'); bool first = true; int enumCount = 0; IEnumerator enumerator = e.GetEnumerator(); if (enumerator != null) { - IBlockingEnumerator be = enumerator as IBlockingEnumerator; - if (be != null) + if (enumerator is IBlockingEnumerator be) { while (be.MoveNext(false)) { @@ -283,17 +289,35 @@ internal static string SmartToString(PSObject so, PSPropertyExpressionFactory ex } } - sb.Append("}"); + sb.Append('}'); 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 (ExtendedTypeSystemException e) + catch (Exception e) when (e is ExtendedTypeSystemException || e is InvalidOperationException) { - // NOTE: we catch all the exceptions, since we do not know - // what the underlying object access would throw + // These exceptions are being caught and handled by returning an empty string when + // the object cannot be stringified due to ETS or an instance in the collection has been modified + s_tracer.TraceWarning($"SmartToString method: Exception during conversion to string, emitting empty string: {e.Message}"); + if (formatErrorObject != null) { formatErrorObject.sourceObject = so; @@ -324,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) @@ -386,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(); @@ -425,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); } } @@ -626,4 +648,3 @@ internal PSPropertyExpression CreateFromExpressionToken(ExpressionToken et, Data private Dictionary _expressionCache; } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs index d04008a1f9a..e7d7b9e75ed 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameter.cs @@ -507,7 +507,7 @@ private static string CatenateTypeArray(Type[] arr) internal static string CatenateStringArray(string[] arr) { StringBuilder sb = new StringBuilder(); - sb.Append("{"); + sb.Append('{'); for (int k = 0; k < arr.Length; k++) { if (k > 0) @@ -518,13 +518,12 @@ internal static string CatenateStringArray(string[] arr) sb.Append(arr[k]); } - sb.Append("}"); + sb.Append('}'); return sb.ToString(); } #endregion - private CommandParameterDefinition _paramDef = null; + private readonly CommandParameterDefinition _paramDef = null; } } - diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs index 22b4c47ee40..a373f18ee81 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/MshParameterAssociation.cs @@ -225,4 +225,3 @@ internal static void HandleComputerNameProperties(PSObject so, List /// Property name pattern to match. - /// true if no further attempts should be made to resolve wildcards. + /// if no further attempts should be made to resolve wildcards. /// public PSPropertyExpression(string s, bool isResolved) { @@ -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); } @@ -354,7 +350,7 @@ private PSPropertyExpressionResult GetValue(PSObject target, bool eatExceptions) } } - private PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out bool wrapped) + private static PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out bool wrapped) { wrapped = false; @@ -375,9 +371,46 @@ private PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out bool wrapp } // private members - private string _stringValue; + private readonly string _stringValue; private bool _isResolved = false; #endregion Private Members + + } + + /// + /// Helper class to do wildcard matching on PSPropertyExpressions. + /// + internal sealed class PSPropertyExpressionFilter + { + /// + /// Initializes a new instance of the class + /// with the specified array of patterns. + /// + /// Array of pattern strings to use. + internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) + { + ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); + + _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; + for (int k = 0; k < wildcardPatternsStrings.Length; k++) + { + _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); + } + } + + /// + /// Try to match the expression against the array of wildcard patterns. + /// The first match short-circuits the search. + /// + /// PSPropertyExpression to test against. + /// True if there is a match, else false. + internal bool IsMatch(PSPropertyExpression expression) + { + string expressionString = expression.ToString(); + return _wildcardPatterns.Any(pattern => pattern.IsMatch(expressionString)); + } + + private readonly WildcardPattern[] _wildcardPatterns; } } diff --git a/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs b/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs index 4a6540afb4d..7d7a993d539 100644 --- a/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs +++ b/src/System.Management.Automation/FormatAndOutput/format-default/format-default.cs @@ -22,4 +22,3 @@ public FormatDefaultCommand() } } } - diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs b/src/System.Management.Automation/FormatAndOutput/out-console/ConsoleLineOutput.cs index b6cde18fb65..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,22 +70,11 @@ 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 PSHostRawUserInterface _rawUserInterface; + 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 @@ -222,6 +158,7 @@ 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 @@ -233,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 @@ -246,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 @@ -301,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); @@ -348,9 +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: @@ -464,7 +395,7 @@ private bool NeedToPrompt /// /// Object to manage prompting. /// - private class PromptHandler + private sealed class PromptHandler { /// /// Prompt handler with the given prompt. @@ -555,12 +486,12 @@ internal PromptResponse PromptUser(PSHostUserInterface console) /// /// Prompt string as passed at initialization. /// - private string _promptString; + private readonly string _promptString; /// /// The cmdlet that uses this prompt helper. /// - private ConsoleLineOutput _callingCmdlet = null; + private readonly ConsoleLineOutput _callingCmdlet = null; } /// @@ -568,28 +499,28 @@ internal PromptResponse PromptUser(PSHostUserInterface console) /// usable width to N-1 (e.g. 80-1) and forcing a call /// to WriteLine() /// - private bool _forceNewLine = true; + private readonly bool _forceNewLine = true; /// /// Use this if IRawConsole is null; /// - private int _fallbackRawConsoleColumnNumber = 80; + private readonly int _fallbackRawConsoleColumnNumber = 80; /// /// Use this if IRawConsole is null; /// - private int _fallbackRawConsoleRowNumber = 40; + private readonly int _fallbackRawConsoleRowNumber = 40; - private WriteLineHelper _writeLineHelper; + private readonly WriteLineHelper _writeLineHelper; /// /// Handler to prompt the user for page breaks /// if this handler is not null, we have prompting. /// - private PromptHandler _prompt = null; + 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; @@ -599,19 +530,19 @@ internal PromptResponse PromptUser(PSHostUserInterface console) private bool _disableLineWrittenEvent = false; /// - /// Refecence to the PSHostUserInterface interface we use. + /// Reference to the PSHostUserInterface interface we use. /// - private PSHostUserInterface _console = null; + private readonly PSHostUserInterface _console = null; /// /// Msh host specific string manipulation helper. /// - private DisplayCells _displayCellsPSHost; + private readonly DisplayCells _displayCellsHost; /// /// Reference to error context to throw Msh exceptions. /// - private TerminatingErrorContext _errorContext = null; + private readonly TerminatingErrorContext _errorContext = null; #endregion } diff --git a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs index e7afeda5e15..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 { @@ -22,7 +22,7 @@ public class OutNullCommand : PSCmdlet /// This parameter specifies the current pipeline object. /// [Parameter(ValueFromPipeline = true)] - public PSObject InputObject { set; get; } = AutomationNull.Value; + public PSObject InputObject { get; set; } = AutomationNull.Value; /// /// Do nothing. @@ -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; } @@ -109,11 +106,10 @@ protected override void ProcessRecord() object inputObjectBase = PSObject.Base(InputObject); // Ignore errors and formatting records, as those can't be captured - if ( - (inputObjectBase != null) && - (!(inputObjectBase is ErrorRecord)) && - (!inputObjectBase.GetType().FullName.StartsWith( - "Microsoft.PowerShell.Commands.Internal.Format", StringComparison.OrdinalIgnoreCase))) + if (inputObjectBase != null && + inputObjectBase is not ErrorRecord && + !inputObjectBase.GetType().FullName.StartsWith( + "Microsoft.PowerShell.Commands.Internal.Format", StringComparison.OrdinalIgnoreCase)) { _outVarResults.Add(InputObject); } @@ -207,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/FormatAndOutput/out-textInterface/OutTextInterface.cs b/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs index 5a6f2bd0683..f520df5787b 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-textInterface/OutTextInterface.cs @@ -90,4 +90,3 @@ private void ProcessWrongTypeLineOutput(object obj) } } } - 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..6ba483b5f6e --- /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 + preview + true + true + enable + + + + + + + diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 7596ecc3f6a..f3e1d0dd9e8 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -2,33 +2,47 @@ PowerShell's System.Management.Automation project - $(NoWarn);CS1570;CS1734 + $(NoWarn);CS1570;CS1734;CA1416;CA2022 System.Management.Automation + + + true + gen\SourceGenerated + + + + + + + + + + + + - + - + - - - - - - - - - - + + + + + + - - + + + + @@ -36,73 +50,17 @@ + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -121,7 +79,10 @@ + + + @@ -130,4 +91,7 @@ + + + diff --git a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs index 23c7a09c4f2..77328bf0257 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/EnumWriter.cs @@ -17,14 +17,14 @@ internal static class EnumWriter private static ModuleBuilder CreateModuleBuilder() { - AssemblyName aName = new AssemblyName(namespacePrefix); + AssemblyName aName = new(namespacePrefix); AssemblyBuilder ab = AssemblyBuilder.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); ModuleBuilder mb = ab.DefineDynamicModule(aName.Name); return mb; } - private static Lazy s_moduleBuilder = new Lazy(CreateModuleBuilder, isThreadSafe: true); - private static object s_moduleBuilderUsageLock = new object(); + 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 81a4c33062f..c2d30f6610e 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodInvocationInfo.cs @@ -18,11 +18,11 @@ public sealed class MethodInvocationInfo /// /// Name of the method to invoke. /// Method parameters. - /// Return value of the method (ok to pass null if the method doesn't return anything). + /// 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; @@ -48,34 +48,31 @@ public MethodInvocationInfo(string name, IEnumerable parameters public KeyedCollection Parameters { get; } /// - /// Return value of the method. Can be null if the method doesn't return anything. + /// Return value of the method. Can be if the method doesn't return anything. /// public MethodParameter ReturnValue { get; } internal IEnumerable GetArgumentsOfType() where T : class { - List result = new List(); + List result = new(); foreach (var methodParameter in this.Parameters) { - if (MethodParameterBindings.In != (methodParameter.Bindings & MethodParameterBindings.In)) + if ((methodParameter.Bindings & MethodParameterBindings.In) != MethodParameterBindings.In) { 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/MethodParameter.cs b/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs index d9b08b27179..f044d4d422f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/MethodParameter.cs @@ -43,7 +43,7 @@ public sealed class MethodParameter public Type ParameterType { get; set; } /// - /// Contents of the ETS type attribute in the CDXML file (or null if that attribute was not specified). + /// Contents of the ETS type attribute in the CDXML file (or if that attribute was not specified). /// The expectation is that the CmdletAdapter will stamp this value onto PSTypeNames of emitted objects. /// public string ParameterTypeName { get; set; } diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs b/src/System.Management.Automation/cimSupport/cmdletization/ObjectModelWrapper.cs index 289ad0c3a76..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(); }; } } @@ -127,7 +110,7 @@ public virtual void StopProcessing() /// /// The object on which to invoke the method. /// Method invocation details. - /// true if successful method invocations should emit downstream the being operated on. + /// if successful method invocations should emit downstream the being operated on. public virtual void ProcessRecord(TObjectInstance objectInstance, MethodInvocationInfo methodInvocationInfo, bool passThru) { throw new NotImplementedException(); @@ -138,7 +121,7 @@ public virtual void ProcessRecord(TObjectInstance objectInstance, MethodInvocati /// /// Query parameters. /// Method invocation details. - /// true if successful method invocations should emit downstream the object instance being operated on. + /// if successful method invocations should emit downstream the object instance being operated on. public virtual void ProcessRecord(QueryBuilder query, MethodInvocationInfo methodInvocationInfo, bool passThru) { throw new NotImplementedException(); @@ -182,7 +165,7 @@ public string ClassName /// /// Name of the class (from the object model handled by this ObjectModelWrapper) that is wrapped by the currently executing cmdlet. - /// This value can be null (i.e. when ClassVersion attribute is omitted in the ps1xml) + /// This value can be (i.e. when ClassVersion attribute is omitted in the ps1xml) /// public string ClassVersion { diff --git a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs index a1ce1d726ec..e71e4c7c04f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs @@ -52,8 +52,8 @@ public abstract class QueryBuilder /// Property name to query on. /// Property values to accept in the query. /// - /// true if should be treated as a containing a wildcard pattern; - /// false otherwise + /// if should be treated as a containing a wildcard pattern; + /// otherwise. /// /// /// Describes how to handle filters that didn't match any objects @@ -69,8 +69,8 @@ public virtual void FilterByProperty(string propertyName, IEnumerable allowedPro /// Property name to query on. /// Property values to reject in the query. /// - /// true if should be treated as a containing a wildcard pattern; - /// false otherwise + /// if should be treated as a containing a wildcard pattern; + /// otherwise. /// /// /// Describes how to handle filters that didn't match any objects diff --git a/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs b/src/System.Management.Automation/cimSupport/cmdletization/ScriptWriter.cs index 3757319c9ab..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; } @@ -234,13 +232,13 @@ private string GetCmdletName(CommonCmdletMetadata cmdletMetadata) return verb + "-" + noun; } - private string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata) + private static string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata) { // Generate the script for the Alias and Obsolete Attribute if any is declared in CDXML - StringBuilder attributes = new StringBuilder(150); + 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 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(); @@ -257,12 +255,12 @@ private string GetCmdletAttributes(CommonCmdletMetadata cmdletMetadata) private Dictionary GetCommonParameters() { - Dictionary commonParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary commonParameters = new(StringComparer.OrdinalIgnoreCase); - InternalParameterMetadata internalParameterMetadata = new InternalParameterMetadata(_objectModelWrapper, false); + InternalParameterMetadata internalParameterMetadata = new(_objectModelWrapper, false); foreach (CompiledCommandParameter compiledCommandParameter in internalParameterMetadata.BindableParameters.Values) { - ParameterMetadata parameterMetadata = new ParameterMetadata(compiledCommandParameter); + ParameterMetadata parameterMetadata = new(compiledCommandParameter); foreach (ParameterSetMetadata psetMetadata in parameterMetadata.ParameterSets.Values) { if (psetMetadata.ValueFromPipeline) @@ -335,7 +333,7 @@ private Dictionary GetCommonParameters() private static List GetCommonParameterSets(Dictionary commonParameters) { - Dictionary parameterSetNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary parameterSetNames = new(StringComparer.OrdinalIgnoreCase); foreach (ParameterMetadata parameter in commonParameters.Values) { foreach (string parameterSetName in parameter.ParameterSets.Keys) @@ -352,7 +350,7 @@ private static List GetCommonParameterSets(Dictionary result = new List(parameterSetNames.Keys); + List result = new(parameterSetNames.Keys); result.Sort(StringComparer.Ordinal); // to have a deterministic order of parameter sets (also means that Ordinal instead of OrdinalIgnoreCase is ok) return result; } @@ -365,7 +363,7 @@ private string GetMethodParameterSet(StaticMethodMetadata staticMethod) private List GetMethodParameterSets(StaticCmdletMetadata staticCmdlet) { - Dictionary parameterSetNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary parameterSetNames = new(StringComparer.OrdinalIgnoreCase); foreach (StaticMethodMetadata method in staticCmdlet.Method) { @@ -386,7 +384,7 @@ private List GetMethodParameterSets(StaticCmdletMetadata staticCmdlet) return new List(parameterSetNames.Keys); } - private Dictionary _staticMethodMetadataToUniqueId = new Dictionary(); + private readonly Dictionary _staticMethodMetadataToUniqueId = new(); private string GetMethodParameterSet(CommonMethodMetadata methodMetadata) { @@ -404,7 +402,7 @@ private string GetMethodParameterSet(CommonMethodMetadata methodMetadata) private List GetMethodParameterSets(InstanceCmdletMetadata instanceCmdlet) { - Dictionary parameterSetNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary parameterSetNames = new(StringComparer.OrdinalIgnoreCase); InstanceMethodMetadata method = instanceCmdlet.Method; string parameterSetName = GetMethodParameterSet(method); @@ -436,7 +434,7 @@ private GetCmdletParameters GetGetCmdletParameters(InstanceCmdletMetadata instan private List GetQueryParameterSets(InstanceCmdletMetadata instanceCmdlet) { - Dictionary parameterSetNames = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary parameterSetNames = new(StringComparer.OrdinalIgnoreCase); var parameters = new List(); bool anyQueryParameters = false; @@ -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)); @@ -555,7 +593,7 @@ private ParameterMetadata GetParameter( parameterName = objectModelParameterName; } - ParameterMetadata parameterMetadata = new ParameterMetadata(parameterName); + ParameterMetadata parameterMetadata = new(parameterName); parameterMetadata.ParameterType = GetDotNetType(parameterTypeMetadata); if (typeof(PSCredential).Equals(parameterMetadata.ParameterType)) { @@ -645,7 +683,7 @@ private ParameterMetadata GetParameter( if (parameterCmdletization.ValidateSet != null) { - List allowedValues = new List(); + List allowedValues = new(); foreach (string allowedValue in parameterCmdletization.ValidateSet) { allowedValues.Add(allowedValue); @@ -788,8 +826,7 @@ private void SetParameters(CommandMetadata commandMetadata, params Dictionary parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + Dictionary parameters = new(StringComparer.OrdinalIgnoreCase); - CommandMetadata commandMetadata = new CommandMetadata( + CommandMetadata commandMetadata = new( name: this.GetCmdletName(cmdletMetadata), commandType: CommandTypes.Cmdlet, isProxyForCmdlet: true, @@ -829,12 +866,12 @@ private static string EscapeModuleNameForHelpComment(string name) { Dbg.Assert(name != null, "Caller should verify name != null"); - StringBuilder result = new StringBuilder(name.Length); + StringBuilder result = new(name.Length); foreach (char c in name) { - if (("\"'`$#".IndexOf(c) == (-1)) && - (!char.IsControl(c)) && - (!char.IsWhiteSpace(c))) + if (!"\"'`$#".Contains(c) + && !char.IsControl(c) + && !char.IsWhiteSpace(c)) { result.Append(c); } @@ -850,10 +887,10 @@ private static List> GetCombinations(params IEnumerable[] x if (x.Length == 1) { - List> result = new List>(); + List> result = new(); foreach (string s in x[0]) { - List subresult = new List(); + List subresult = new(); subresult.Add(s); result.Add(subresult); } @@ -866,12 +903,12 @@ private static List> GetCombinations(params IEnumerable[] x Array.Copy(x, 0, smallX, 0, smallX.Length); List> smallResult = GetCombinations(smallX); - List> result = new List>(); + List> result = new(); foreach (List smallSubresult in smallResult) { foreach (string s in x[x.Length - 1]) { - List newsubresult = new List(smallSubresult); + List newsubresult = new(smallSubresult); newsubresult.Add(s); result.Add(newsubresult); } @@ -941,7 +978,7 @@ private static void MultiplyParameterSets( foreach (ParameterMetadata parameter in parameters.Values) { - List> oldParameterSets = new List>(parameter.ParameterSets); + List> oldParameterSets = new(parameter.ParameterSets); parameter.ParameterSets.Clear(); foreach (KeyValuePair oldParameterSet in oldParameterSets) @@ -964,7 +1001,7 @@ private static IEnumerable MultiplyParameterSets( string parameterSetNameTemplate, // {0} is the original parameter set, other ones are taken from the otherParameterSets array params IEnumerable[] otherParameterSets) { - List result = new List(); + List result = new(); List> combinations = GetCombinations(otherParameterSets); foreach (List combination in combinations) @@ -1086,7 +1123,7 @@ private static void GenerateSingleMethodParameterProcessing( prefix); } - if (MethodParameterBindings.In == (methodParameterBindings & MethodParameterBindings.In)) + if ((methodParameterBindings & MethodParameterBindings.In) == MethodParameterBindings.In) { Dbg.Assert(cmdletParameterName != null, "Called should verify cmdletParameterName!=null for 'in' parameters"); @@ -1114,7 +1151,7 @@ private static void GenerateSingleMethodParameterProcessing( CodeGeneration.EscapeSingleQuotedStringContent(cmdletParameterTypeName), CodeGeneration.EscapeSingleQuotedStringContent(methodParameterBindings.ToString())); - if (MethodParameterBindings.In == (methodParameterBindings & MethodParameterBindings.In)) + if ((methodParameterBindings & MethodParameterBindings.In) == MethodParameterBindings.In) { output.WriteLine("{0}}}", prefix); } @@ -1139,8 +1176,8 @@ private void GenerateMethodParametersProcessing( out string outputTypeAttributeDeclaration) { methodParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - StringBuilder outputTypeAttributeDeclarationBuilder = new StringBuilder(); - StringWriter output = new StringWriter(CultureInfo.InvariantCulture); + StringBuilder outputTypeAttributeDeclarationBuilder = new(); + StringWriter output = new(CultureInfo.InvariantCulture); output.WriteLine(" $__cmdletization_methodParameters = [System.Collections.Generic.List[Microsoft.PowerShell.Cmdletization.MethodParameter]]::new()"); output.WriteLine(); @@ -1162,7 +1199,11 @@ string parameterSetName in MultiplyParameterSets( GetMethodParameterSet(method), StaticMethodParameterSetTemplate, commonParameterSets)) { - if (!firstParameterSet) output.Write(", "); + if (!firstParameterSet) + { + output.Write(", "); + } + firstParameterSet = false; output.Write("'{0}'", CodeGeneration.EscapeSingleQuotedStringContent(parameterSetName)); } @@ -1170,8 +1211,8 @@ string parameterSetName in output.WriteLine(") -contains $_ } {"); } - List typesOfOutParameters = new List(); - List etsTypesOfOutParameters = new List(); + List typesOfOutParameters = new(); + List etsTypesOfOutParameters = new(); if (method.Parameters != null) { foreach (StaticMethodParameterMetadata methodParameter in method.Parameters) @@ -1222,7 +1263,7 @@ string parameterSetName in methodParameter.ParameterName, methodParameterBindings); - if (MethodParameterBindings.Out == (methodParameterBindings & MethodParameterBindings.Out)) + if ((methodParameterBindings & MethodParameterBindings.Out) == MethodParameterBindings.Out) { typesOfOutParameters.Add(dotNetTypeOfParameter); etsTypesOfOutParameters.Add(methodParameter.Type.ETSType); @@ -1246,7 +1287,7 @@ string parameterSetName in CodeGeneration.EscapeSingleQuotedStringContent(method.ReturnValue.Type.ETSType)); } - if (MethodParameterBindings.Out == (methodParameterBindings & MethodParameterBindings.Out)) + if ((methodParameterBindings & MethodParameterBindings.Out) == MethodParameterBindings.Out) { typesOfOutParameters.Add(dotNetTypeOfParameter); etsTypesOfOutParameters.Add(method.ReturnValue.Type.ETSType); @@ -1303,7 +1344,7 @@ private void GenerateMethodParametersProcessing( { methodParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); outputTypeAttributeDeclaration = string.Empty; - StringWriter output = new StringWriter(CultureInfo.InvariantCulture); + StringWriter output = new(CultureInfo.InvariantCulture); output.WriteLine(" $__cmdletization_methodParameters = [System.Collections.Generic.List[Microsoft.PowerShell.Cmdletization.MethodParameter]]::new()"); output.WriteLine(" switch -exact ($PSCmdlet.ParameterSetName) { "); @@ -1313,15 +1354,19 @@ private void GenerateMethodParametersProcessing( bool firstParameterSet = true; foreach (string parameterSetName in MultiplyParameterSets(GetMethodParameterSet(method), InstanceMethodParameterSetTemplate, commonParameterSets, queryParameterSets)) { - if (!firstParameterSet) output.Write(", "); + if (!firstParameterSet) + { + output.Write(", "); + } + firstParameterSet = false; output.Write("'{0}'", CodeGeneration.EscapeSingleQuotedStringContent(parameterSetName)); } output.WriteLine(") -contains $_ } {"); - List typesOfOutParameters = new List(); - List etsTypesOfOutParameters = new List(); + List typesOfOutParameters = new(); + List etsTypesOfOutParameters = new(); if (method.Parameters != null) { foreach (InstanceMethodParameterMetadata methodParameter in method.Parameters) @@ -1363,7 +1408,7 @@ private void GenerateMethodParametersProcessing( methodParameter.ParameterName, methodParameterBindings); - if (MethodParameterBindings.Out == (methodParameterBindings & MethodParameterBindings.Out)) + if ((methodParameterBindings & MethodParameterBindings.Out) == MethodParameterBindings.Out) { typesOfOutParameters.Add(dotNetTypeOfParameter); etsTypesOfOutParameters.Add(methodParameter.Type.ETSType); @@ -1387,7 +1432,7 @@ private void GenerateMethodParametersProcessing( CodeGeneration.EscapeSingleQuotedStringContent(method.ReturnValue.Type.ETSType)); } - if (MethodParameterBindings.Out == (methodParameterBindings & MethodParameterBindings.Out)) + if ((methodParameterBindings & MethodParameterBindings.Out) == MethodParameterBindings.Out) { typesOfOutParameters.Add(dotNetTypeOfParameter); etsTypesOfOutParameters.Add(method.ReturnValue.Type.ETSType); @@ -1444,7 +1489,7 @@ private void GenerateMethodParametersProcessing( } } - private void GenerateIfBoundParameter( + private static void GenerateIfBoundParameter( IEnumerable commonParameterSets, IEnumerable methodParameterSets, ParameterMetadata cmdletParameterMetadata, @@ -1455,7 +1500,11 @@ private void GenerateIfBoundParameter( foreach (string queryParameterSetName in cmdletParameterMetadata.ParameterSets.Keys) foreach (string parameterSetName in MultiplyParameterSets(queryParameterSetName, InstanceQueryParameterSetTemplate, commonParameterSets, methodParameterSets)) { - if (!firstParameterSet) output.Write(", "); + if (!firstParameterSet) + { + output.Write(", "); + } + firstParameterSet = false; output.Write("'{0}'", CodeGeneration.EscapeSingleQuotedStringContent(parameterSetName)); } @@ -1615,14 +1664,14 @@ private void GenerateQueryParametersProcessing( out Dictionary queryParameters) { queryParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); - StringWriter output = new StringWriter(CultureInfo.InvariantCulture); + StringWriter output = new(CultureInfo.InvariantCulture); output.WriteLine(" $__cmdletization_queryBuilder = $__cmdletization_objectModelWrapper.GetQueryBuilder()"); GetCmdletParameters getCmdletParameters = GetGetCmdletParameters(instanceCmdlet); if (getCmdletParameters.QueryableProperties != null) { - foreach (PropertyMetadata property in getCmdletParameters.QueryableProperties.Where(p => 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); @@ -1725,7 +1774,7 @@ private void GenerateQueryParametersProcessing( if (instanceCmdlet != null) { - ParameterMetadata inputObjectParameter = new ParameterMetadata("InputObject", _objectInstanceType.MakeArrayType()); + ParameterMetadata inputObjectParameter = new("InputObject", _objectInstanceType.MakeArrayType()); ParameterSetMetadata.ParameterFlags inputObjectFlags = ParameterSetMetadata.ParameterFlags.ValueFromPipeline; if (queryParameters.Count > 0) @@ -1761,10 +1810,10 @@ private void GenerateQueryParametersProcessing( inputObjectParameter.Attributes.Add(new ValidateNotNullAttribute()); inputObjectParameter.ParameterSets.Clear(); - ParameterSetMetadata inputObjectPSet = new ParameterSetMetadata( + ParameterSetMetadata inputObjectPSet = new( int.MinValue, // non-positional inputObjectFlags, - null); // no help message + helpMessage: null); inputObjectParameter.ParameterSets.Add(ScriptWriter.InputObjectQueryParameterSetName, inputObjectPSet); queryParameters.Add(inputObjectParameter.Name, inputObjectParameter); } @@ -1859,9 +1908,9 @@ private void GenerateQueryParametersProcessing( private string GetHelpDirectiveForExternalHelp() { - StringBuilder output = new StringBuilder(); + StringBuilder output = new(); - if (GenerationOptions.HelpXml == (_generationOptions & GenerationOptions.HelpXml)) + if ((_generationOptions & GenerationOptions.HelpXml) == GenerationOptions.HelpXml) { output.AppendFormat( CultureInfo.InvariantCulture, @@ -1874,7 +1923,7 @@ private string GetHelpDirectiveForExternalHelp() private void WriteCmdlet(TextWriter output, StaticCmdletMetadata staticCmdlet) { - string attributeString = this.GetCmdletAttributes(staticCmdlet.CmdletMetadata); + string attributeString = GetCmdletAttributes(staticCmdlet.CmdletMetadata); Dictionary commonParameters = this.GetCommonParameters(); List commonParameterSets = GetCommonParameterSets(commonParameters); @@ -1948,9 +1997,9 @@ private static void AddPassThruParameter(IDictionary if (!outParametersArePresent) { - ParameterMetadata passThruParameter = new ParameterMetadata("PassThru", typeof(SwitchParameter)); + ParameterMetadata passThruParameter = new("PassThru", typeof(SwitchParameter)); passThruParameter.ParameterSets.Clear(); - ParameterSetMetadata passThruPSet = new ParameterSetMetadata(int.MinValue, 0, null); + ParameterSetMetadata passThruPSet = new(int.MinValue, 0, null); passThruParameter.ParameterSets.Add(ParameterAttribute.AllParameterSets, passThruPSet); commonParameters.Add(passThruParameter.Name, passThruParameter); @@ -1959,7 +2008,7 @@ private static void AddPassThruParameter(IDictionary private void WriteCmdlet(TextWriter output, InstanceCmdletMetadata instanceCmdlet) { - string attributeString = this.GetCmdletAttributes(instanceCmdlet.CmdletMetadata); + string attributeString = GetCmdletAttributes(instanceCmdlet.CmdletMetadata); Dictionary commonParameters = this.GetCommonParameters(); List commonParameterSets = GetCommonParameterSets(commonParameters); @@ -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); @@ -2017,7 +2066,7 @@ private void WriteCmdlet(TextWriter output, InstanceCmdletMetadata instanceCmdle private string GetOutputAttributeForGetCmdlet() { - StringBuilder result = new StringBuilder(); + StringBuilder result = new(); result.AppendFormat( CultureInfo.InvariantCulture, "[OutputType([{0}])]", @@ -2055,7 +2104,7 @@ private void WriteGetCmdlet(TextWriter output) { Dictionary commonParameters = this.GetCommonParameters(); List commonParameterSets = GetCommonParameterSets(commonParameters); - List methodParameterSets = new List(); + List methodParameterSets = new(); methodParameterSets.Add(string.Empty); List queryParameterSets = GetQueryParameterSets(null); @@ -2066,7 +2115,7 @@ private void WriteGetCmdlet(TextWriter output) CommonCmdletMetadata cmdletMetadata = this.GetGetCmdletMetadata(); Dbg.Assert(cmdletMetadata != null, "xsd should ensure that cmdlet metadata element is always present"); CommandMetadata commandMetadata = this.GetCommandMetadata(cmdletMetadata); - string attributeString = this.GetCmdletAttributes(cmdletMetadata); + string attributeString = GetCmdletAttributes(cmdletMetadata); GetCmdletParameters getCmdletParameters = this.GetGetCmdletParameters(null); if (!string.IsNullOrEmpty(getCmdletParameters.DefaultCmdletParameterSet)) @@ -2100,7 +2149,7 @@ private void WriteGetCmdlet(TextWriter output) /* 1 */ CodeGeneration.EscapeSingleQuotedStringContent(commandMetadata.Name)); } - private static object s_enumCompilationLock = new object(); + private static readonly object s_enumCompilationLock = new(); private static void CompileEnum(EnumMetadataEnum enumMetadata) { @@ -2176,11 +2225,11 @@ internal void PopulatePSModuleInfo(PSModuleInfo moduleInfo) moduleInfo.SetModuleType(ModuleType.Cim); moduleInfo.SetVersion(new Version(_cmdletizationMetadata.Class.Version)); - Hashtable cmdletizationData = new Hashtable(StringComparer.OrdinalIgnoreCase); + Hashtable cmdletizationData = new(StringComparer.OrdinalIgnoreCase); cmdletizationData.Add(PrivateDataKey_ClassName, _cmdletizationMetadata.Class.ClassName); cmdletizationData.Add(PrivateDataKey_ObjectModelWrapper, _objectModelWrapper); - Hashtable privateData = new Hashtable(StringComparer.OrdinalIgnoreCase); + Hashtable privateData = new(StringComparer.OrdinalIgnoreCase); privateData.Add(PrivateDataKey_CmdletsOverObjects, cmdletizationData); moduleInfo.PrivateData = privateData; } @@ -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/cim/WildcardPatternToCimQueryParser.cs b/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs index 4ce8a854487..48a47da7289 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/cim/WildcardPatternToCimQueryParser.cs @@ -20,7 +20,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// internal class WildcardPatternToCimQueryParser : WildcardPatternParser { - private readonly StringBuilder _result = new StringBuilder(); + private readonly StringBuilder _result = new(); private bool _needClientSideFiltering; protected override void AppendLiteralCharacter(char c) @@ -79,13 +79,13 @@ protected override void AppendCharacterRangeToBracketExpression(char startOfChar // 93 = ] // 94 = ^ // 95 = _ - if ((91 <= startOfCharacterRange) && (startOfCharacterRange <= 94)) + if ((startOfCharacterRange >= 91) && (startOfCharacterRange <= 94)) { startOfCharacterRange = (char)90; _needClientSideFiltering = true; } - if ((91 <= endOfCharacterRange) && (endOfCharacterRange <= 94)) + if ((endOfCharacterRange >= 91) && (endOfCharacterRange <= 94)) { endOfCharacterRange = (char)95; _needClientSideFiltering = true; 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.objectModel.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs index e2b87d4adf9..7274c3bb954 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs @@ -2237,4 +2237,3 @@ public string Value } } } - diff --git a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs index f4892b51bc1..dd1c6023cdb 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs @@ -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); @@ -2071,22 +2159,22 @@ private void Write15_PropertyMetadata(string n, string ns, global::Microsoft.Pow { if (ci == Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@RegularQuery && ((object)(ai) != null)) { - if (((object)ai) != null && !(ai is global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery)) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@RegularQuery"); + if (((object)ai) != null && ai is not global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@RegularQuery"); Write13_WildcardablePropertyQuery(@"RegularQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery)ai), false, false); } else if (ci == Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@ExcludeQuery && ((object)(ai) != null)) { - if (((object)ai) != null && !(ai is global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery)) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@ExcludeQuery"); + if (((object)ai) != null && ai is not global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@ExcludeQuery"); Write13_WildcardablePropertyQuery(@"ExcludeQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.WildcardablePropertyQuery)ai), false, false); } else if (ci == Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MaxValueQuery && ((object)(ai) != null)) { - if (((object)ai) != null && !(ai is global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MaxValueQuery"); + if (((object)ai) != null && ai is not global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MaxValueQuery"); Write14_PropertyQuery(@"MaxValueQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)ai), false, false); } else if (ci == Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MinValueQuery && ((object)(ai) != null)) { - if (((object)ai) != null && !(ai is global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MinValueQuery"); + if (((object)ai) != null && ai is not global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery) throw CreateMismatchChoiceException(@"Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery", @"ItemsElementName", @"Microsoft.PowerShell.Cmdletization.Xml.ItemsChoiceType.@MinValueQuery"); Write14_PropertyQuery(@"MinValueQuery", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11", ((global::Microsoft.PowerShell.Cmdletization.Xml.PropertyQuery)ai), false, false); } else if ((object)(ai) != null) @@ -10020,4 +10108,3 @@ public override System.Xml.Serialization.XmlSerializer GetSerializer(System.Type } } } - 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 3a7e29a4c14..202c3e98e48 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -21,14 +21,12 @@ 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 { private static PSAdaptedProperty GetCimPropertyAdapter(CimProperty property, object baseObject, string propertyName) { - PSAdaptedProperty propertyToAdd = new PSAdaptedProperty(propertyName, property); + PSAdaptedProperty propertyToAdd = new(propertyName, property); propertyToAdd.baseObject = baseObject; // propertyToAdd.adapter = this; return propertyToAdd; @@ -50,7 +48,7 @@ private static PSAdaptedProperty GetCimPropertyAdapter(CimProperty property, obj private static PSAdaptedProperty GetPSComputerNameAdapter(CimInstance cimInstance) { - PSAdaptedProperty psComputerNameProperty = new PSAdaptedProperty(RemotingConstants.ComputerNameNoteProperty, cimInstance); + PSAdaptedProperty psComputerNameProperty = new(RemotingConstants.ComputerNameNoteProperty, cimInstance); psComputerNameProperty.baseObject = cimInstance; // psComputerNameProperty.adapter = this; return psComputerNameProperty; @@ -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, @@ -73,7 +70,7 @@ public override System.Collections.ObjectModel.Collection Get throw new PSInvalidOperationException(msg); } - Collection result = new Collection(); + Collection result = new(); if (cimInstance.CimInstanceProperties != null) { @@ -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, @@ -135,7 +131,7 @@ public override PSAdaptedProperty GetProperty(object baseObject, string property return null; } - /// + /// public override PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) { if (predicate == null) @@ -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; } @@ -242,25 +229,20 @@ public override object GetPropertyValue(PSAdaptedProperty adaptedProperty) throw new ArgumentNullException(nameof(adaptedProperty)); } - private void AddTypeNameHierarchy(IList typeNamesWithNamespace, IList typeNamesWithoutNamespace, string namespaceName, string className) + private static void AddTypeNameHierarchy(IList typeNamesWithNamespace, IList typeNamesWithoutNamespace, string namespaceName, string className) { 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 List GetInheritanceChain(CimInstance cimInstance) + private static List GetInheritanceChain(CimInstance cimInstance) { - List inheritanceChain = new List(); + List inheritanceChain = new(); CimClass cimClass = cimInstance.CimClass; Dbg.Assert(cimClass != null, "CimInstance should always have ClassDecl"); while (cimClass != null) @@ -285,8 +267,7 @@ private List GetInheritanceChain(CimInstance cimInstance) /// public override Collection GetTypeNameHierarchy(object baseObject) { - var cimInstance = baseObject as CimInstance; - if (cimInstance == null) + if (baseObject is not CimInstance cimInstance) { throw new ArgumentNullException(nameof(baseObject)); } @@ -362,13 +343,12 @@ public override bool IsSettable(PSAdaptedProperty adaptedProperty) return false; } - CimProperty cimProperty = adaptedProperty.Tag as CimProperty; - if (cimProperty == null) + if (adaptedProperty.Tag is not CimProperty cimProperty) { return false; } - bool isReadOnly = (CimFlags.ReadOnly == (cimProperty.Flags & CimFlags.ReadOnly)); + bool isReadOnly = ((cimProperty.Flags & CimFlags.ReadOnly) == CimFlags.ReadOnly); bool isSettable = !isReadOnly; return isSettable; } @@ -379,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/AliasInfo.cs b/src/System.Management.Automation/engine/AliasInfo.cs index bd209c6afe8..94acdecf628 100644 --- a/src/System.Management.Automation/engine/AliasInfo.cs +++ b/src/System.Management.Automation/engine/AliasInfo.cs @@ -337,7 +337,7 @@ internal void SetOptions(ScopedItemOptions newOptions, bool force) /// If ResolvedCommand returns null, this property will /// return the name of the command that could not be resolved. /// If ResolvedCommand has not yet been called or was able - /// to resolve the command, this this property will return null. + /// to resolve the command, this property will return null. /// internal string UnresolvedCommandName { get; private set; } diff --git a/src/System.Management.Automation/engine/ApplicationInfo.cs b/src/System.Management.Automation/engine/ApplicationInfo.cs index 9e785aee935..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 @@ -52,7 +52,7 @@ internal ApplicationInfo(string name, string path, ExecutionContext context) : b _context = context; } - private ExecutionContext _context; + private readonly ExecutionContext _context; #endregion ctor /// @@ -113,7 +113,10 @@ public override SessionStateEntryVisibility Visibility return _context.EngineSessionState.CheckApplicationVisibility(Path); } - set { throw PSTraceSource.NewNotImplementedException(); } + set + { + throw PSTraceSource.NewNotImplementedException(); + } } /// 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/ArgumentTypeConverterAttribute.cs b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs index e9b6be3caff..596a4142e93 100644 --- a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs +++ b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs @@ -25,15 +25,13 @@ internal ArgumentTypeConverterAttribute(params Type[] types) _convertTypes = types; } - private Type[] _convertTypes; + private readonly Type[] _convertTypes; internal Type TargetType { get { - return _convertTypes == null - ? null - : _convertTypes.LastOrDefault(); + return _convertTypes?.LastOrDefault(); } } @@ -67,9 +65,7 @@ internal object Transform(EngineIntrinsics engineIntrinsics, object inputData, b else temp = result; - PSReference reference = temp as PSReference; - - if (reference == null) + if (temp is not PSReference reference) { throw new PSInvalidCastException("InvalidCastExceptionReferenceTypeExpected", null, ExtendedTypeSystem.ReferenceTypeExpected); @@ -210,4 +206,3 @@ internal static void ThrowPSInvalidBooleanArgumentCastException(Type resultType, } } } - 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 593dd89d30d..dac3a2ac377 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; @@ -86,7 +87,7 @@ namespace System.Management.Automation /// validates the argument as a whole. If the argument value may /// be an enumerable, you can derive from /// which will take care of unrolling the enumerable and validate each element individually. - /// It is also recommended to override to return a readable string + /// It is also recommended to override to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, the string[] command argument will be validated. @@ -153,7 +154,7 @@ protected ValidateArgumentsAttribute() /// and override the /// /// abstract method, after which they can apply the attribute to their parameters. - /// It is also recommended to override to return a readable string + /// It is also recommended to override to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, each string command argument will be validated. @@ -326,7 +327,10 @@ public abstract class CmdletCommonMetadataAttribute : CmdletMetadataAttribute /// public bool SupportsTransactions { - get { return _supportsTransactions; } + get + { + return _supportsTransactions; + } set { @@ -492,7 +496,7 @@ public OutputTypeAttribute(params string[] type) /// [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] - public PSTypeName[] Type { get; private set; } + public PSTypeName[] Type { get; } /// /// Attributes implemented by a provider can use: @@ -507,7 +511,7 @@ public OutputTypeAttribute(params string[] type) [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] ParameterSetName { - get => _parameterSetName ?? (_parameterSetName = new[] { ParameterAttribute.AllParameterSets }); + get => _parameterSetName ??= new[] { ParameterAttribute.AllParameterSets }; set => _parameterSetName = value; } @@ -607,6 +611,7 @@ public ParameterAttribute(string experimentName, ExperimentAction experimentActi public ExperimentAction ExperimentAction { get; } internal bool ToHide => EffectiveAction == ExperimentAction.Hide; + internal bool ToShow => EffectiveAction == ExperimentAction.Show; /// @@ -752,7 +757,7 @@ public class PSTypeNameAttribute : Attribute { /// /// - public string PSTypeName { get; private set; } + public string PSTypeName { get; } /// /// Creates a new PSTypeNameAttribute. @@ -839,8 +844,7 @@ public sealed class ValidateLengthAttribute : ValidateEnumeratedArgumentsAttribu /// For invalid arguments. protected override void ValidateElement(object element) { - string objectString = element as string; - if (objectString == null) + if (element is not string objectString) { throw new ValidationMetadataException( "ValidateLengthNotString", @@ -938,27 +942,27 @@ public sealed class ValidateRangeAttribute : ValidateEnumeratedArgumentsAttribut /// public object MinRange { get; } - private IComparable _minComparable; + private readonly IComparable _minComparable; /// /// Gets the attribute's maximum range. /// public object MaxRange { get; } - private IComparable _maxComparable; + private readonly IComparable _maxComparable; /// /// The range values and the value to validate will all be converted to the promoted type. /// If minRange and maxRange are the same type, /// - private Type _promotedType; + private readonly Type _promotedType; /// /// Gets the name of the predefined range. /// internal ValidateRangeKind? RangeKind { get => _rangeKind; } - private ValidateRangeKind? _rangeKind; + private readonly ValidateRangeKind? _rangeKind; /// /// Validates that each parameter argument falls in the range specified by @@ -1083,8 +1087,14 @@ public ValidateRangeAttribute(ValidateRangeKind kind) : base() _rangeKind = kind; } - private void ValidateRange(object element, ValidateRangeKind rangeKind) + 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) { @@ -1093,7 +1103,7 @@ private void ValidateRange(object element, ValidateRangeKind rangeKind) innerException: null, Metadata.ValidateRangeElementType, element.GetType().Name, - typeof(int).Name); + nameof(Int32)); } object resultValue; @@ -1209,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; @@ -1249,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; + } + } } /// @@ -1265,17 +1350,17 @@ public sealed class ValidatePatternAttribute : ValidateEnumeratedArgumentsAttrib /// /// Gets or sets the Regex options to be used in the validation. /// - public RegexOptions Options { set; get; } = RegexOptions.IgnoreCase; + public RegexOptions Options { get; set; } = RegexOptions.IgnoreCase; /// /// 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; } @@ -1337,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; } @@ -1532,7 +1617,7 @@ public abstract class CachedValidValuesGeneratorBase : IValidateSetValuesGenerat { // Cached valid values. private string[] _validValues; - private int _validValuesCacheExpiration; + private readonly int _validValuesCacheExpiration; /// /// Initializes a new instance of the class. @@ -1591,22 +1676,22 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute { // We can use either static '_validValues' or dynamic valid values list generated by instance // of 'validValuesGenerator'. - private string[] _validValues; - private IValidateSetValuesGenerator validValuesGenerator = null; + private readonly string[] _validValues; + private readonly IValidateSetValuesGenerator validValuesGenerator = null; // The valid values generator cache works across 'ValidateSetAttribute' instances. - private static ConcurrentDictionary s_ValidValuesGeneratorCache = + private static readonly ConcurrentDictionary s_ValidValuesGeneratorCache = new ConcurrentDictionary(); /// /// 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; } @@ -1619,6 +1704,7 @@ public sealed class ValidateSetAttribute : ValidateEnumeratedArgumentsAttribute /// /// Gets the valid values in the set. /// + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "")] public IList ValidValues { get @@ -1725,20 +1811,23 @@ 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)); } } /// /// Allows dynamically generate set of values for /// +#nullable enable public interface IValidateSetValuesGenerator { /// /// Gets valid values. /// + /// A non-null array of non-null strings. string[] GetValidValues(); } +#nullable restore /// /// Validates that each parameter argument is Trusted data. @@ -1763,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); } } } @@ -1778,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 { /// @@ -1790,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 { /// @@ -1802,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 { /// @@ -1821,7 +1920,7 @@ public AllowEmptyCollectionAttribute() { } [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class ValidateDriveAttribute : ValidateArgumentsAttribute { - private string[] _validRootDrives; + private readonly string[] _validRootDrives; /// /// Gets the values in the set. @@ -1857,8 +1956,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin Metadata.ValidateNotNullFailure); } - var path = arguments as string; - if (path == null) + if (arguments is not string path) { throw new ValidationMetadataException( "PathArgumentIsNotValid", @@ -1988,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()) @@ -2007,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. /// @@ -2035,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", @@ -2047,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. @@ -2066,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", @@ -2098,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 @@ -2117,7 +2292,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin /// and override the /// abstract method, after which they /// can apply the attribute to their parameters. - /// It is also recommended to override to return a readable + /// It is also recommended to override to return a readable /// string similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If multiple transformations are defined on a parameter, they will be invoked in series, /// each getting the output of the previous transformation. diff --git a/src/System.Management.Automation/engine/AutomationEngine.cs b/src/System.Management.Automation/engine/AutomationEngine.cs index 38d985c7634..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,16 +105,25 @@ 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 6dec6aeabb7..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. /// @@ -31,4 +31,3 @@ public static class AutomationNull #endregion public_property } } - 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/ComAdapter.cs b/src/System.Management.Automation/engine/COM/ComAdapter.cs index 75e95b6082a..18a87cc71fa 100644 --- a/src/System.Management.Automation/engine/COM/ComAdapter.cs +++ b/src/System.Management.Automation/engine/COM/ComAdapter.cs @@ -32,7 +32,7 @@ internal static string GetComTypeName(string clsid) StringBuilder firstType = new StringBuilder("System.__ComObject"); firstType.Append("#{"); firstType.Append(clsid); - firstType.Append("}"); + firstType.Append('}'); return firstType.ToString(); } diff --git a/src/System.Management.Automation/engine/COM/ComDispatch.cs b/src/System.Management.Automation/engine/COM/ComDispatch.cs index 9199ef9f48f..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, @@ -33,8 +34,8 @@ void Invoke( [MarshalAs(UnmanagedType.LPStruct)] Guid iid, int lcid, COM.INVOKEKIND wFlags, - [In, Out] [MarshalAs(UnmanagedType.LPArray)] COM.DISPPARAMS[] paramArray, - out object pVarResult, + [In, Out][MarshalAs(UnmanagedType.LPArray)] COM.DISPPARAMS[] paramArray, + 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..ea8b8b96d79 100644 --- a/src/System.Management.Automation/engine/COM/ComInvoker.cs +++ b/src/System.Management.Automation/engine/COM/ComInvoker.cs @@ -7,9 +7,6 @@ using COM = System.Runtime.InteropServices.ComTypes; -// Disable obsolete warnings about VarEnum and COM-marshaling APIs in CoreCLR -#pragma warning disable 618 - namespace System.Management.Automation { internal static class ComInvoker @@ -280,7 +277,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 +294,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 +302,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/ComMethod.cs b/src/System.Management.Automation/engine/COM/ComMethod.cs index 32552c6bd14..7f6f3ca8816 100644 --- a/src/System.Management.Automation/engine/COM/ComMethod.cs +++ b/src/System.Management.Automation/engine/COM/ComMethod.cs @@ -30,8 +30,8 @@ internal ComMethodInformation(bool hasvarargs, bool hasoptional, ParameterInform /// internal class ComMethod { - private Collection _methods = new Collection(); - private COM.ITypeInfo _typeInfo; + private readonly Collection _methods = new Collection(); + private readonly COM.ITypeInfo _typeInfo; /// /// Initializes new instance of ComMethod class. @@ -133,4 +133,3 @@ internal object InvokeMethod(PSMethod method, object[] arguments) } } } - diff --git a/src/System.Management.Automation/engine/COM/ComProperty.cs b/src/System.Management.Automation/engine/COM/ComProperty.cs index 2de108a872e..9ac1a28656b 100644 --- a/src/System.Management.Automation/engine/COM/ComProperty.cs +++ b/src/System.Management.Automation/engine/COM/ComProperty.cs @@ -22,7 +22,7 @@ internal class ComProperty private int _setterIndex; private int _setterByRefIndex; private int _getterIndex; - private COM.ITypeInfo _typeInfo; + private readonly COM.ITypeInfo _typeInfo; /// /// Initializes a new instance of ComProperty. @@ -130,7 +130,7 @@ internal bool IsSettable { get { - return _hasSetter | _hasSetterByRef; + return _hasSetter || _hasSetterByRef; } } @@ -366,7 +366,7 @@ public override string ToString() { StringBuilder builder = new StringBuilder(); builder.Append(this.GetDefinition()); - builder.Append(" "); + builder.Append(' '); if (IsGettable) { builder.Append("{get} "); diff --git a/src/System.Management.Automation/engine/COM/ComTypeInfo.cs b/src/System.Management.Automation/engine/COM/ComTypeInfo.cs index 6a03f31c9ac..eab6122a002 100644 --- a/src/System.Management.Automation/engine/COM/ComTypeInfo.cs +++ b/src/System.Management.Automation/engine/COM/ComTypeInfo.cs @@ -30,9 +30,9 @@ internal class ComTypeInfo /// /// Member variables. /// - private Dictionary _properties = null; - private Dictionary _methods = null; - private COM.ITypeInfo _typeinfo = null; + private readonly Dictionary _properties = null; + private readonly Dictionary _methods = null; + private readonly COM.ITypeInfo _typeinfo = null; private Guid _guid = Guid.Empty; /// @@ -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; @@ -307,4 +302,3 @@ internal static COM.ITypeInfo GetDispatchTypeInfoFromCoClassTypeInfo(COM.ITypeIn } } } - diff --git a/src/System.Management.Automation/engine/COM/ComUtil.cs b/src/System.Management.Automation/engine/COM/ComUtil.cs index 4d5a9964339..8cd5e73b835 100644 --- a/src/System.Management.Automation/engine/COM/ComUtil.cs +++ b/src/System.Management.Automation/engine/COM/ComUtil.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation.ComInterop; using System.Runtime.InteropServices; @@ -17,7 +16,7 @@ namespace System.Management.Automation /// /// Defines a utility class that is used by COM adapter. /// - internal class ComUtil + internal static class ComUtil { // HResult error code '-2147352573' - Member not found. internal const int DISP_E_MEMBERNOTFOUND = unchecked((int)0x80020003); @@ -98,7 +97,7 @@ internal static string GetMethodSignatureFromFuncDesc(COM.ITypeInfo typeinfo, CO } } - builder.Append(")"); + builder.Append(')'); return builder.ToString(); } @@ -142,9 +141,6 @@ private static string GetStringFromCustomType(COM.ITypeInfo typeinfo, IntPtr ref return "UnknownCustomtype"; } - // Disable obsolete warning about VarEnum in CoreCLR -#pragma warning disable 618 - /// /// This function gets a string representation of the Type Descriptor /// This is used in generating signature for Properties and Methods. @@ -260,8 +256,6 @@ internal static Type GetTypeFromTypeDesc(COM.TYPEDESC typedesc) return VarEnumSelector.GetTypeForVarEnum(vt); } -#pragma warning restore 618 - /// /// Converts a FuncDesc out of GetFuncDesc into a MethodInformation. /// @@ -365,53 +359,4 @@ internal static ComMethodInformation[] GetMethodInformationArray(COM.ITypeInfo t return returnValue; } } - - /// - /// Defines an enumerator that represent a COM collection object. - /// - internal class ComEnumerator : IEnumerator - { - private COM.IEnumVARIANT _enumVariant; - private object[] _element; - - private ComEnumerator(COM.IEnumVARIANT enumVariant) - { - _enumVariant = enumVariant; - _element = new object[1]; - } - - public object Current - { - get { return _element[0]; } - } - - public bool MoveNext() - { - _element[0] = null; - int result = _enumVariant.Next(1, _element, IntPtr.Zero); - return result == 0; - } - - public void Reset() - { - _element[0] = null; - _enumVariant.Reset(); - } - - /// - /// Try to create an enumerator for a COM object. - /// - /// - /// A 'ComEnumerator' instance, or null if we cannot create an enumerator for the COM object. - /// - internal static ComEnumerator Create(object comObject) - { - if (comObject == null || !comObject.GetType().IsCOMObject) { return null; } - - // The passed-in COM object could already be a IEnumVARIANT interface. - // e.g. user call '_NewEnum()' on a COM collection interface. - var enumVariant = comObject as COM.IEnumVARIANT; - return enumVariant != null ? new ComEnumerator(enumVariant) : null; - } - } } diff --git a/src/System.Management.Automation/engine/ChildrenCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/ChildrenCmdletProviderInterfaces.cs index 85cff4c8efb..57c871b2f76 100644 --- a/src/System.Management.Automation/engine/ChildrenCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ChildrenCmdletProviderInterfaces.cs @@ -761,8 +761,8 @@ internal bool HasChild( #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data } @@ -784,4 +784,3 @@ public enum ReturnContainers ReturnAllContainers } } - diff --git a/src/System.Management.Automation/engine/CmdletFamilyProviderInterfaces.cs b/src/System.Management.Automation/engine/CmdletFamilyProviderInterfaces.cs index 27537b21601..061b630d353 100644 --- a/src/System.Management.Automation/engine/CmdletFamilyProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/CmdletFamilyProviderInterfaces.cs @@ -102,9 +102,8 @@ internal ProviderIntrinsics(SessionStateInternal sessionState) #region private data - private InternalCommand _cmdlet; + private readonly InternalCommand _cmdlet; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/CmdletInfo.cs b/src/System.Management.Automation/engine/CmdletInfo.cs index 2861fa3a8b6..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 { @@ -149,7 +149,7 @@ public string Verb } } - private string _verb = string.Empty; + private readonly string _verb = string.Empty; /// /// Gets the noun of the cmdlet. @@ -162,7 +162,7 @@ public string Noun } } - private string _noun = string.Empty; + private readonly string _noun = string.Empty; internal static bool SplitCmdletName(string name, out string verb, out string noun) { @@ -224,7 +224,7 @@ public PSSnapInInfo PSSnapIn } } - private PSSnapInInfo _PSSnapin; + private readonly PSSnapInInfo _PSSnapin; /// /// Gets the name of the PSSnapin the cmdlet is implemented in. @@ -279,7 +279,7 @@ public Type ImplementingType } } - private Type _implementingType = null; + private readonly Type _implementingType = null; /// /// Gets the synopsis of the cmdlet. @@ -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) @@ -535,8 +532,7 @@ internal override CommandMetadata CommandMetadata { get { - return _cmdletMetadata ?? - (_cmdletMetadata = CommandMetadata.Get(this.Name, this.ImplementingType, Context)); + return _cmdletMetadata ??= CommandMetadata.Get(this.Name, this.ImplementingType, Context); } } @@ -548,7 +544,7 @@ internal override bool ImplementsDynamicParameters { if (ImplementingType != null) { - return (ImplementingType.GetInterface(typeof(IDynamicParameters).Name, true) != null); + return (ImplementingType.GetInterface(nameof(IDynamicParameters), true) != null); } else { diff --git a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs index 069201589af..b9cf67b29e6 100644 --- a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs +++ b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs @@ -202,18 +202,9 @@ internal void BindCommandLineParameters(Collection arg internal void BindCommandLineParametersNoValidation(Collection arguments) { var psCompiledScriptCmdlet = this.Command as PSScriptCmdlet; - if (psCompiledScriptCmdlet != null) - { - psCompiledScriptCmdlet.PrepareForBinding(this.CommandLineParameters); - } - - // Add the passed in arguments to the unboundArguments collection - - foreach (CommandParameterInternal argument in arguments) - { - UnboundArguments.Add(argument); - } + psCompiledScriptCmdlet?.PrepareForBinding(this.CommandLineParameters); + InitUnboundArguments(arguments); CommandMetadata cmdletMetadata = _commandMetadata; // Clear the warningSet at the beginning. _warningSet.Clear(); @@ -232,7 +223,7 @@ internal void BindCommandLineParametersNoValidation(Collection GetQualifiedParameter } /// - /// Get the aliases of the the current cmdlet. + /// Get the aliases of the current cmdlet. /// /// private List GetAliasOfCurrentCmdlet() @@ -632,8 +622,7 @@ private Dictionary GetDefaultParameterVa foreach (DictionaryEntry entry in DefaultParameterValues) { - string key = entry.Key as string; - if (key == null) + if (entry.Key is not string key) { continue; } @@ -1052,10 +1041,7 @@ private bool RestoreParameter(CommandParameterInternal argumentToBind, MergedCom _commandMetadata.ImplementsDynamicParameters, "The metadata for the dynamic parameters should only be available if the command supports IDynamicParameters"); - if (_dynamicParameterBinder != null) - { - _dynamicParameterBinder.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter); - } + _dynamicParameterBinder?.BindParameter(argumentToBind.ParameterName, argumentToBind.ArgumentValue, parameter.Parameter); break; } @@ -1064,134 +1050,56 @@ private bool RestoreParameter(CommandParameterInternal argumentToBind, MergedCom } /// - /// Binds the actual arguments to only the formal parameters - /// for only the parameters in the specified parameter set. + /// Validate the given named parameter against the specified parameter set, + /// and then bind the argument to the parameter. /// - /// - /// The parameter set used to bind the arguments. - /// - /// - /// The arguments that should be attempted to bind to the parameters of the specified - /// parameter binder. - /// - /// - /// if multiple parameters are found matching the name. - /// or - /// if no match could be found. - /// or - /// If argument transformation fails. - /// or - /// The argument could not be coerced to the appropriate type for the parameter. - /// or - /// The parameter argument transformation, prerequisite, or validation failed. - /// or - /// If the binding to the parameter fails. - /// - private Collection BindParameters(uint parameterSets, Collection arguments) + protected override void BindNamedParameter( + uint parameterSets, + CommandParameterInternal argument, + MergedCompiledCommandParameter parameter) { - Collection result = new Collection(); - - foreach (CommandParameterInternal argument in arguments) + if ((parameter.Parameter.ParameterSetFlags & parameterSets) == 0 && + !parameter.Parameter.IsInAllSets) { - if (!argument.ParameterNameSpecified) - { - result.Add(argument); - continue; - } - - // We don't want to throw an exception yet because - // the parameter might be a positional argument or it - // might match up to a dynamic parameter + string parameterSetName = BindableParameters.GetParameterSetName(parameterSets); - MergedCompiledCommandParameter parameter = - BindableParameters.GetMatchingParameter( + ParameterBindingException bindingException = + new ParameterBindingException( + ErrorCategory.InvalidArgument, + this.Command.MyInvocation, + errorPosition: null, argument.ParameterName, - false, true, - new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent)); + parameterType: null, + typeSpecified: null, + ParameterBinderStrings.ParameterNotInParameterSet, + "ParameterNotInParameterSet", + parameterSetName); - // If the parameter is not in the specified parameter set, - // throw a binding exception - - if (parameter != null) - { - // Now check to make sure it hasn't already been - // bound by looking in the boundParameters collection - - if (BoundParameters.ContainsKey(parameter.Parameter.Name)) - { - ParameterBindingException bindingException = - new ParameterBindingException( - ErrorCategory.InvalidArgument, - this.InvocationInfo, - GetParameterErrorExtent(argument), - argument.ParameterName, - null, - null, - ParameterBinderStrings.ParameterAlreadyBound, - nameof(ParameterBinderStrings.ParameterAlreadyBound)); - - // Multiple values assigned to the same parameter. - // Not caused by default parameter binding - throw bindingException; - } - - if ((parameter.Parameter.ParameterSetFlags & parameterSets) == 0 && - !parameter.Parameter.IsInAllSets) - { - string parameterSetName = BindableParameters.GetParameterSetName(parameterSets); - - ParameterBindingException bindingException = - new ParameterBindingException( - ErrorCategory.InvalidArgument, - this.Command.MyInvocation, - null, - argument.ParameterName, - null, - null, - ParameterBinderStrings.ParameterNotInParameterSet, - "ParameterNotInParameterSet", - parameterSetName); - - // Might be caused by default parameter binding - if (!DefaultParameterBindingInUse) - { - throw bindingException; - } - else - { - ThrowElaboratedBindingException(bindingException); - } - } - - try - { - BindParameter(parameterSets, argument, parameter, - ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock); - } - catch (ParameterBindingException pbex) - { - if (!DefaultParameterBindingInUse) - { - throw; - } - - ThrowElaboratedBindingException(pbex); - } - } - else if (argument.ParameterName.Equals(Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal)) + // Might be caused by default parameter binding + if (!DefaultParameterBindingInUse) { - // We sometimes send a magic parameter from a remote machine with the values referenced via - // a using expression ($using:x). We then access these values via PSBoundParameters, so - // "bind" them here. - DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue); + throw bindingException; } else { - result.Add(argument); + ThrowElaboratedBindingException(bindingException); } } - return result; + try + { + BindParameter(parameterSets, argument, parameter, + ParameterBindingFlags.ShouldCoerceType | ParameterBindingFlags.DelayBindScriptBlock); + } + catch (ParameterBindingException pbex) + { + if (!DefaultParameterBindingInUse) + { + throw; + } + + ThrowElaboratedBindingException(pbex); + } } /// @@ -1259,17 +1167,6 @@ private static bool IsParameterScriptBlockBindable(MergedCompiledCommandParamete return result; } - /// - /// Binds the specified parameters to the cmdlet. - /// - /// - /// The parameters to bind. - /// - internal override Collection BindParameters(Collection parameters) - { - return BindParameters(uint.MaxValue, parameters); - } - /// /// Binds the specified argument to the specified parameter using the appropriate /// parameter binder. If the argument is of type ScriptBlock and the parameter takes @@ -1548,8 +1445,7 @@ private bool BindParameter( BoundObsoleteParameterNames.Add(parameter.Parameter.Name); - if (ObsoleteParameterWarningList == null) - ObsoleteParameterWarningList = new List(); + ObsoleteParameterWarningList ??= new List(); ObsoleteParameterWarningList.Add(warningRecord); } @@ -1724,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( @@ -1815,7 +1714,7 @@ private void HandleCommandLineDynamicParameters(out ParameterBindingException ou ReparseUnboundArguments(); - UnboundArguments = BindParameters(_currentParameterSetFlag, UnboundArguments); + UnboundArguments = BindNamedParameters(_currentParameterSetFlag, UnboundArguments); } using (ParameterBinderBase.bindingTracer.TraceScope( @@ -2352,7 +2251,7 @@ private Collection GetMissingMandatoryParameters // If we have one or the other, we can latch onto that set without difficulty uint uniqueSetThatTakesPipelineInput = 0; - if ((foundSetThatTakesPipelineInputByValue & foundSetThatTakesPipelineInputByPropertyName) && + if (foundSetThatTakesPipelineInputByValue && foundSetThatTakesPipelineInputByPropertyName && (setThatTakesPipelineInputByValue == setThatTakesPipelineInputByPropertyName)) { uniqueSetThatTakesPipelineInput = setThatTakesPipelineInputByValue; @@ -2546,7 +2445,7 @@ private void IgnoreOtherMandatoryParameterSets(uint otherMandatorySetsToBeIgnore } } - private uint NewParameterSetPromptingData( + private static uint NewParameterSetPromptingData( Dictionary promptingData, MergedCompiledCommandParameter parameter, ParameterSetSpecificMetadata parameterSetMetadata, @@ -2757,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) { @@ -2771,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); @@ -3087,7 +2986,7 @@ internal static string BuildMissingParamsString(Collection - internal Cmdlet Command { get; private set; } + internal Cmdlet Command { get; } #region DefaultParameterBindingStructures @@ -4023,8 +3926,7 @@ private HashSet BoundObsoleteParameterNames { get { - return _boundObsoleteParameterNames ?? - (_boundObsoleteParameterNames = new HashSet(StringComparer.OrdinalIgnoreCase)); + return _boundObsoleteParameterNames ??= new HashSet(StringComparer.OrdinalIgnoreCase); } } @@ -4144,7 +4046,7 @@ internal ReflectionParameterBinder CommonParametersBinder private ReflectionParameterBinder _commonParametersBinder; - private class DelayedScriptBlockArgument + private sealed class DelayedScriptBlockArgument { // Remember the parameter binder so we know when to invoke the script block // and when to use the evaluated argument. @@ -4214,7 +4116,7 @@ private bool BindPipelineParameter( /*argumentAst*/null, parameterValue, false); - flags = flags & ~ParameterBindingFlags.DelayBindScriptBlock; + flags &= ~ParameterBindingFlags.DelayBindScriptBlock; result = BindParameter(_currentParameterSetFlag, param, parameter, flags); if (result) @@ -4319,7 +4221,7 @@ private void RestoreDefaultParameterValues(IEnumerableThe original event with handler added. private object InPlaceAdd(object handler) { + Requires.NotNull(handler); VerifyHandler(handler); - ComEventSink comEventSink = ComEventSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, true); + ComEventsSink comEventSink = ComEventsSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, true); comEventSink.AddHandler(_dispid, handler); return this; } @@ -92,18 +88,13 @@ private object InPlaceAdd(object handler) /// The original event with handler removed. private object InPlaceSubtract(object handler) { + Requires.NotNull(handler); VerifyHandler(handler); - ComEventSink comEventSink = ComEventSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, false); - if (comEventSink != null) - { - comEventSink.RemoveHandler(_dispid, handler); - } + ComEventsSink comEventSink = ComEventsSink.FromRuntimeCallableWrapper(_rcw, _sourceIid, false); + comEventSink?.RemoveHandler(_dispid, handler); return this; } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/CollectionExtensions.cs b/src/System.Management.Automation/engine/ComInterop/CollectionExtensions.cs index fc64b27986d..6ccb5877624 100644 --- a/src/System.Management.Automation/engine/ComInterop/CollectionExtensions.cs +++ b/src/System.Management.Automation/engine/ComInterop/CollectionExtensions.cs @@ -1,6 +1,7 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; namespace System.Management.Automation.ComInterop @@ -38,4 +39,3 @@ internal static T[] AddLast(this IList list, T item) } } } - diff --git a/src/System.Management.Automation/engine/ComInterop/ComBinder.cs b/src/System.Management.Automation/engine/ComInterop/ComBinder.cs index 9ba83fc4de3..a88abc09f1e 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComBinder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComBinder.cs @@ -1,20 +1,12 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Management.Automation.Language; -//using Microsoft.Scripting.Utils; using System.Dynamic; using System.Linq; - -[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Dynamic")] +using System.Linq.Expressions; +using System.Management.Automation.Language; +using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop { @@ -30,27 +22,22 @@ internal static class ComBinder /// True if the object is a COM object, false otherwise. public static bool IsComObject(object value) { - return - (value != null) && - (!WinRTHelper.IsWinRTType(value.GetType())) && - ComObject.IsComObject(value); - } - - public static bool CanComBind(object value) - { - return IsComObject(value) || value is IPseudoComObject; + return value != null && Marshal.IsComObject(value); } /// /// Tries to perform binding of the dynamic get member operation. /// /// An instance of the that represents the details of the dynamic operation. - /// The target of the dynamic operation. + /// The target of the dynamic operation. /// The new representing the result of the binding. /// True if member evaluation may be delayed. /// True if operation was bound successfully; otherwise, false. public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result, bool delayInvocation) { + Requires.NotNull(binder); + Requires.NotNull(instance); + if (TryGetMetaObject(ref instance)) { var comGetMember = new ComGetMemberBinder(binder, delayInvocation); @@ -62,26 +49,11 @@ public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject in result.Restrictions ); } - return true; } - else - { - result = null; - return false; - } - } - /// - /// Tries to perform binding of the dynamic get member operation. - /// - /// An instance of the that represents the details of the dynamic operation. - /// The target of the dynamic operation. - /// The new representing the result of the binding. - /// True if operation was bound successfully; otherwise, false. - public static bool TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, out DynamicMetaObject result) - { - return TryBindGetMember(binder, instance, out result, false); + result = null; + return false; } /// @@ -94,39 +66,43 @@ 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); + Requires.NotNull(instance); + Requires.NotNull(value); + if (TryGetMetaObject(ref instance)) { result = instance.BindSetMember(binder, value); result = new DynamicMetaObject(result.Expression, result.Restrictions.Merge(value.PSGetMethodArgumentRestriction())); return true; } - else - { - result = null; - return false; - } + + result = null; + return false; } /// /// Tries to perform binding of the dynamic invoke operation. /// /// An instance of the that represents the details of the dynamic operation. - /// The target of the dynamic operation. + /// The target of the dynamic operation. /// An array of instances - arguments to the invoke member operation. /// The new representing the result of the binding. /// True if operation was bound successfully; otherwise, false. public static bool TryBindInvoke(InvokeBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) { + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); + if (TryGetMetaObjectInvoke(ref instance)) { result = instance.BindInvoke(binder, args); return true; } - else - { - result = null; - return false; - } + + result = null; + return false; } /// @@ -134,12 +110,16 @@ public static bool TryBindInvoke(InvokeBinder binder, DynamicMetaObject instance /// /// An instance of the that represents the details of the dynamic operation. /// True if this is for setting a property, false otherwise. - /// The target of the dynamic operation. + /// The target of the dynamic operation. /// An array of instances - arguments to the invoke member operation. /// The new representing the result of the binding. /// 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); + Requires.NotNull(instance); + Requires.NotNull(args); + if (TryGetMetaObject(ref instance)) { var comInvokeMember = new ComInvokeMemberBinder(binder, isSetProperty); @@ -163,58 +143,60 @@ public static bool TryBindInvokeMember(InvokeMemberBinder binder, bool isSetProp return true; } - else - { - result = null; - return false; - } + + result = null; + return false; } /// /// Tries to perform binding of the dynamic get index operation. /// /// An instance of the that represents the details of the dynamic operation. - /// The target of the dynamic operation. + /// The target of the dynamic operation. /// An array of instances - arguments to the invoke member operation. /// The new representing the result of the binding. /// True if operation was bound successfully; otherwise, false. public static bool TryBindGetIndex(GetIndexBinder binder, DynamicMetaObject instance, DynamicMetaObject[] args, out DynamicMetaObject result) { + Requires.NotNull(binder); + Requires.NotNull(instance); + Requires.NotNull(args); + if (TryGetMetaObjectInvoke(ref instance)) { result = instance.BindGetIndex(binder, args); return true; } - else - { - result = null; - return false; - } + + result = null; + return false; } /// /// Tries to perform binding of the dynamic set index operation. /// /// An instance of the that represents the details of the dynamic operation. - /// The target of the dynamic operation. + /// The target of the dynamic operation. /// An array of instances - arguments to the invoke member operation. /// The representing the value for the set index operation. /// The new representing the result of the binding. /// 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); + Requires.NotNull(instance); + Requires.NotNull(args); + Requires.NotNull(value); + if (TryGetMetaObjectInvoke(ref instance)) { result = instance.BindSetIndex(binder, args, value); result = new DynamicMetaObject(result.Expression, result.Restrictions.Merge(value.PSGetMethodArgumentRestriction())); - return true; } - else - { - result = null; - return false; - } + + result = null; + return false; } /// @@ -226,6 +208,9 @@ 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); + Requires.NotNull(instance); + if (IsComObject(instance.Value)) { // Converting a COM object to any interface is always considered possible - it will result in @@ -239,7 +224,7 @@ public static bool TryConvert(ConvertBinder binder, DynamicMetaObject instance, ), BindingRestrictions.GetExpressionRestriction( Expression.Call( - typeof(ComObject).GetMethod("IsComObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic), + typeof(ComBinder).GetMethod(nameof(ComBinder.IsComObject), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public), Helpers.Convert(instance.Expression, typeof(object)) ) ) @@ -252,26 +237,17 @@ public static bool TryConvert(ConvertBinder binder, DynamicMetaObject instance, return false; } - /// - /// Gets the member names associated with the object. - /// This function can operate only with objects for which returns true. - /// - /// The object for which member names are requested. - /// The collection of member names. - public static IEnumerable GetDynamicMemberNames(object value) - { - return ComObject.ObjectToComObject(value).GetMemberNames(false); - } - /// /// Gets the member names of the data-like members associated with the object. /// This function can operate only with objects for which returns true. /// /// The object for which member names are requested. /// The collection of member names. - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal static IList GetDynamicDataMemberNames(object value) { + Requires.NotNull(value); + Requires.Condition(IsComObject(value), nameof(value)); + return ComObject.ObjectToComObject(value).GetMemberNames(true); } @@ -282,10 +258,11 @@ internal static IList GetDynamicDataMemberNames(object value) /// The object for which data members are requested. /// The enumeration of names of data members for which to retrieve values. /// The collection of pairs that represent data member's names and their data. - [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal static IList> GetDynamicDataMembers(object value, IEnumerable names) { + Requires.NotNull(value); + Requires.Condition(IsComObject(value), nameof(value)); + return ComObject.ObjectToComObject(value).GetMembers(names); } @@ -316,9 +293,9 @@ private static bool TryGetMetaObjectInvoke(ref DynamicMetaObject instance) return true; } - if (instance.Value is IPseudoComObject) + if (instance.Value is IPseudoComObject o) { - instance = ((IPseudoComObject)instance.Value).GetMetaObject(instance.Expression); + instance = o.GetMetaObject(instance.Expression); return true; } @@ -331,13 +308,13 @@ private static bool TryGetMetaObjectInvoke(ref DynamicMetaObject instance) internal class ComGetMemberBinder : GetMemberBinder { private readonly GetMemberBinder _originalBinder; - internal bool _CanReturnCallables; + 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; + _canReturnCallables = canReturnCallables; } public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion) @@ -347,15 +324,14 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy public override int GetHashCode() { - return _originalBinder.GetHashCode() ^ (_CanReturnCallables ? 1 : 0); + return _originalBinder.GetHashCode() ^ (_canReturnCallables ? 1 : 0); } public override bool Equals(object obj) { - ComGetMemberBinder other = obj as ComGetMemberBinder; - return other != null && - _CanReturnCallables == other._CanReturnCallables && - _originalBinder.Equals(other._originalBinder); + return obj is ComGetMemberBinder other + && _canReturnCallables == other._canReturnCallables + && _originalBinder.Equals(other._originalBinder); } } @@ -367,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; @@ -399,6 +375,3 @@ public override bool Equals(object obj) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComBinderHelpers.cs b/src/System.Management.Automation/engine/ComInterop/ComBinderHelpers.cs index 50fdbec6855..2351ce6c900 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComBinderHelpers.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComBinderHelpers.cs @@ -1,18 +1,16 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +#pragma warning disable 618 // CurrencyWrapper is obsolete + +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Dynamic; -using System.Runtime.InteropServices; -using System.Collections.Generic; using System.Globalization; +using System.Linq.Expressions; using System.Management.Automation.Language; +using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop { @@ -22,26 +20,23 @@ internal static bool PreferPut(Type type, bool holdsNull) { Debug.Assert(type != null); - if (type.IsValueType || type.IsArray) return true; - - if (type == typeof(string) || - type == typeof(DBNull) || - holdsNull || - type == typeof(System.Reflection.Missing) || - type == typeof(CurrencyWrapper)) + if (type.IsValueType + || type.IsArray + || type == typeof(string) + || type == typeof(DBNull) + || holdsNull + || type == typeof(System.Reflection.Missing) + || type == typeof(CurrencyWrapper)) { return true; } - else - { - return false; - } + + return false; } internal static bool IsByRef(DynamicMetaObject mo) { - ParameterExpression pe = mo.Expression as ParameterExpression; - return pe != null && pe.IsByRef; + return mo.Expression is ParameterExpression pe && pe.IsByRef; } internal static bool IsPSReferenceArg(DynamicMetaObject o) @@ -50,7 +45,7 @@ internal static bool IsPSReferenceArg(DynamicMetaObject o) return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(PSReference<>); } - // this helper prepares arguments for COM binding by transforming ByVal StongBox arguments + // This helper prepares arguments for COM binding by transforming ByVal StrongBox arguments // into ByRef expressions that represent the argument's Value fields. internal static bool[] ProcessArgumentsForCom(ComMethodDesc method, ref DynamicMetaObject[] args, List temps, List initTemps) @@ -67,7 +62,6 @@ internal static bool[] ProcessArgumentsForCom(ComMethodDesc method, ref DynamicM // set new arg infos to their original values or set default ones // we will do this fixup early so that we can assume we always have // arginfos in COM binder. - if (IsByRef(curArgument)) { newArgs[i] = curArgument; @@ -91,7 +85,7 @@ internal static bool[] ProcessArgumentsForCom(ComMethodDesc method, ref DynamicM ); PSReference value = curArgument.Value as PSReference; - object boxedValue = value != null ? value.Value : null; + object boxedValue = value?.Value; newArgs[i] = new DynamicMetaObject( boxedValueAccessor, @@ -105,12 +99,15 @@ internal static bool[] ProcessArgumentsForCom(ComMethodDesc method, ref DynamicM { if ((method.ParameterInformation != null) && (i < method.ParameterInformation.Length)) { - newArgs[i] = new DynamicMetaObject(curArgument.CastOrConvertMethodArgument( - method.ParameterInformation[i].parameterType, - i.ToString(CultureInfo.InvariantCulture), - method.Name, - temps, - initTemps), curArgument.Restrictions); + newArgs[i] = new DynamicMetaObject( + curArgument.CastOrConvertMethodArgument( + method.ParameterInformation[i].parameterType, + i.ToString(CultureInfo.InvariantCulture), + method.Name, + allowCastingToByRefLikeType: false, + temps, + initTemps), + curArgument.Restrictions); } else { @@ -130,14 +127,10 @@ internal static BindingRestrictions GetTypeRestrictionForDynamicMetaObject(Dynam { if (obj.Value == null && obj.HasValue) { - // If the meta object holds a null value, create an instance restriction for checking null + //If the meta object holds a null value, create an instance restriction for checking null return BindingRestrictions.GetInstanceRestriction(obj.Expression, null); } - return BindingRestrictions.GetTypeRestriction(obj.Expression, obj.LimitType); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComClassMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/ComClassMetaObject.cs index 1c246d8f170..6d94aa7e0a6 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComClassMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComClassMetaObject.cs @@ -1,16 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Dynamic; -//using AstUtils = Microsoft.Scripting.Ast.Utils; +using System.Linq.Expressions; using AstUtils = System.Management.Automation.Interpreter.Utils; namespace System.Management.Automation.ComInterop @@ -27,7 +19,7 @@ public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder return new DynamicMetaObject( Expression.Call( AstUtils.Convert(Expression, typeof(ComTypeClassDesc)), - typeof(ComTypeClassDesc).GetMethod("CreateInstance") + typeof(ComTypeClassDesc).GetMethod(nameof(ComTypeClassDesc.CreateInstance)) ), BindingRestrictions.Combine(args).Merge( BindingRestrictions.GetTypeRestriction(Expression, typeof(ComTypeClassDesc)) @@ -36,6 +28,3 @@ public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComDispIds.cs b/src/System.Management.Automation/engine/ComInterop/ComDispIds.cs deleted file mode 100644 index 95ce0b4ed6e..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComDispIds.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -namespace System.Management.Automation.ComInterop -{ - internal static class ComDispIds - { - internal const int DISPID_VALUE = 0; - internal const int DISPID_PROPERTYPUT = -3; - internal const int DISPID_NEWENUM = -4; - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComEventDesc.cs index b601fb7bb39..961532a8a5e 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComEventDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComEventDesc.cs @@ -1,16 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject +using System; namespace System.Management.Automation.ComInterop { internal class ComEventDesc { - internal Guid sourceIID; - internal int dispid; - }; + public Guid SourceIID; + public int Dispid; + } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventSink.cs b/src/System.Management.Automation/engine/ComInterop/ComEventSink.cs deleted file mode 100644 index c4146cf08b3..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComEventSink.cs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Reflection; -using System.Runtime.InteropServices; -using ComTypes = System.Runtime.InteropServices.ComTypes; -//using Microsoft.Scripting.Utils; - -namespace System.Management.Automation.ComInterop -{ - /// - /// This class implements an event sink for a particular RCW. - /// Unlike the implementation of events in TlbImp'd assemblies, - /// we will create only one event sink per RCW (theoretically RCW might have - /// several ComEventSink evenk sinks - but all these implement different source interfaces). - /// Each ComEventSink contains a list of ComEventSinkMethod objects - which represent - /// a single method on the source interface an a multicast delegate to redirect - /// the calls. Notice that we are chaining multicast delegates so that same - /// ComEventSinkMethod can invoke multiple event handlers). - /// - /// ComEventSink implements an IDisposable pattern to Unadvise from the connection point. - /// Typically, when RCW is finalized the corresponding Dispose will be triggered by - /// ComEventSinksContainer finalizer. Notice that lifetime of ComEventSinksContainer - /// is bound to the lifetime of the RCW. - /// - internal sealed class ComEventSink : MarshalByRefObject, IReflect, IDisposable - { - #region private fields - - private Guid _sourceIid; - private ComTypes.IConnectionPoint _connectionPoint; - private int _adviseCookie; - private List _comEventSinkMethods; - private object _lockObject = new object(); // We cannot lock on ComEventSink since it causes a DoNotLockOnObjectsWithWeakIdentity warning - - #endregion - - #region private classes - - /// - /// Contains a methods DISPID (in a string formatted of "[DISPID=N]" - /// and a chained list of delegates to invoke. - /// - private class ComEventSinkMethod - { - public string _name; - public Func _handlers; - } - #endregion - - #region ctor - - private ComEventSink(object rcw, Guid sourceIid) - { - Initialize(rcw, sourceIid); - } - - #endregion - - private void Initialize(object rcw, Guid sourceIid) - { - _sourceIid = sourceIid; - _adviseCookie = -1; - - Debug.Assert(_connectionPoint == null, "re-initializing event sink w/o unadvising from connection point"); - - ComTypes.IConnectionPointContainer cpc = rcw as ComTypes.IConnectionPointContainer; - if (cpc == null) - throw Error.COMObjectDoesNotSupportEvents(); - - cpc.FindConnectionPoint(ref _sourceIid, out _connectionPoint); - if (_connectionPoint == null) - throw Error.COMObjectDoesNotSupportSourceInterface(); - - // Read the comments for ComEventSinkProxy about why we need it - ComEventSinkProxy proxy = new ComEventSinkProxy(this, _sourceIid); - _connectionPoint.Advise(proxy.GetTransparentProxy(), out _adviseCookie); - } - - #region static methods - - public static ComEventSink FromRuntimeCallableWrapper(object rcw, Guid sourceIid, bool createIfNotFound) - { - List comEventSinks = ComEventSinksContainer.FromRuntimeCallableWrapper(rcw, createIfNotFound); - - if (comEventSinks == null) - { - return null; - } - - ComEventSink comEventSink = null; - lock (comEventSinks) - { - foreach (ComEventSink sink in comEventSinks) - { - if (sink._sourceIid == sourceIid) - { - comEventSink = sink; - break; - } - else if (sink._sourceIid == Guid.Empty) - { - // we found a ComEventSink object that - // was previously disposed. Now we will reuse it. - sink.Initialize(rcw, sourceIid); - comEventSink = sink; - } - } - - if (comEventSink == null && createIfNotFound == true) - { - comEventSink = new ComEventSink(rcw, sourceIid); - comEventSinks.Add(comEventSink); - } - } - - return comEventSink; - } - - #endregion - - public void AddHandler(int dispid, object func) - { - string name = string.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid); - - lock (_lockObject) - { - ComEventSinkMethod sinkMethod; - sinkMethod = FindSinkMethod(name); - - if (sinkMethod == null) - { - if (_comEventSinkMethods == null) - { - _comEventSinkMethods = new List(); - } - - sinkMethod = new ComEventSinkMethod(); - sinkMethod._name = name; - _comEventSinkMethods.Add(sinkMethod); - } - - sinkMethod._handlers += new SplatCallSite(func).Invoke; - } - } - - public void RemoveHandler(int dispid, object func) - { - string name = string.Format(CultureInfo.InvariantCulture, "[DISPID={0}]", dispid); - - lock (_lockObject) - { - ComEventSinkMethod sinkEntry = FindSinkMethod(name); - if (sinkEntry == null) - { - return; - } - - // Remove the delegate from multicast delegate chain. - // We will need to find the delegate that corresponds - // to the func handler we want to remove. This will be - // easy since we Target property of the delegate object - // is a ComEventCallContext object. - Delegate[] delegates = sinkEntry._handlers.GetInvocationList(); - foreach (Delegate d in delegates) - { - SplatCallSite callContext = d.Target as SplatCallSite; - if (callContext != null && callContext._callable.Equals(func)) - { - sinkEntry._handlers -= d as Func; - break; - } - } - - // If the delegates chain is empty - we can remove - // corresponding ComEvenSinkEntry - if (sinkEntry._handlers == null) - _comEventSinkMethods.Remove(sinkEntry); - - // We can Unadvise from the ConnectionPoint if no more sink entries - // are registered for this interface - // (calling Dispose will call IConnectionPoint.Unadvise). - if (_comEventSinkMethods.Count == 0) - { - // notice that we do not remove - // ComEventSinkEntry from the list, we will re-use this data structure - // if a new handler needs to be attached. - Dispose(); - } - } - } - - public object ExecuteHandler(string name, object[] args) - { - ComEventSinkMethod site; - site = FindSinkMethod(name); - - if (site != null && site._handlers != null) - { - return site._handlers(args); - } - - return null; - } - - #region IReflect - - #region Unimplemented members - - public FieldInfo GetField(string name, BindingFlags bindingAttr) - { - return null; - } - - public FieldInfo[] GetFields(BindingFlags bindingAttr) - { - return Array.Empty(); - } - - public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) - { - return Array.Empty(); - } - - public MemberInfo[] GetMembers(BindingFlags bindingAttr) - { - return Array.Empty(); - } - - public MethodInfo GetMethod(string name, BindingFlags bindingAttr) - { - return null; - } - - public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) - { - return null; - } - - public MethodInfo[] GetMethods(BindingFlags bindingAttr) - { - return Array.Empty(); - } - - public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) - { - return null; - } - - public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) - { - return null; - } - - public PropertyInfo[] GetProperties(BindingFlags bindingAttr) - { - return Array.Empty(); - } - - #endregion - - public Type UnderlyingSystemType - { - get - { - return typeof(object); - } - } - - public object InvokeMember( - string name, - BindingFlags invokeAttr, - Binder binder, - object target, - object[] args, - ParameterModifier[] modifiers, - CultureInfo culture, - string[] namedParameters) - { - return ExecuteHandler(name, args); - } - - #endregion - - #region IDisposable - - public void Dispose() - { - DisposeAll(); - GC.SuppressFinalize(this); - } - - #endregion - - ~ComEventSink() - { - DisposeAll(); - } - - private void DisposeAll() - { - if (_connectionPoint == null) - { - return; - } - - if (_adviseCookie == -1) - { - return; - } - - try - { - _connectionPoint.Unadvise(_adviseCookie); - - // _connectionPoint has entered the CLR in the constructor - // for this object and hence its ref counter has been increased - // by us. We have not exposed it to other components and - // hence it is safe to call RCO on it w/o worrying about - // killing the RCW for other objects that link to it. - Marshal.ReleaseComObject(_connectionPoint); - } - catch (Exception ex) - { - // if something has gone wrong, and the object is no longer attached to the CLR, - // the Unadvise is going to throw. In this case, since we're going away anyway, - // we'll ignore the failure and quietly go on our merry way. - COMException exCOM = ex as COMException; - if (exCOM != null && exCOM.ErrorCode == ComHresults.CONNECT_E_NOCONNECTION) - { - Debug.Assert(false, "IConnectionPoint::Unadvise returned CONNECT_E_NOCONNECTION."); - throw; - } - } - finally - { - _connectionPoint = null; - _adviseCookie = -1; - _sourceIid = Guid.Empty; - } - } - - private ComEventSinkMethod FindSinkMethod(string name) - { - if (_comEventSinkMethods == null) - return null; - - ComEventSinkMethod site; - site = _comEventSinkMethods.Find(element => element._name == name); - - return site; - } - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventSinkProxy.cs b/src/System.Management.Automation/engine/ComInterop/ComEventSinkProxy.cs deleted file mode 100644 index 7fbb23f932d..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComEventSinkProxy.cs +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -using System.Globalization; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Remoting; -using System.Runtime.Remoting.Messaging; -using System.Runtime.Remoting.Proxies; - -//using Microsoft.Scripting.Utils; - -namespace System.Management.Automation.ComInterop -{ - /// - /// ComEventSinkProxy class is responsible for handling QIs for sourceIid - /// on instances of ComEventSink. - /// - /// Background: When a COM even sink advises to a connection point it is - /// supposed to hand over the dispinterface. Now, some hosts will trust - /// the COM client to pass the correct pointer, but some will not. - /// E.g. Excel's implementation of Connection Points will not cause a - /// QI on the pointer that has been passed, however Word will QI the - /// pointer to return the required interface. - /// - /// ComEventSink does not, strongly speaking, implements the interface - /// that it claims to implement - it is just "faking" it by using IReflect. - /// Thus, Word's QIs on the pointer passed to ICP::Advise would fail. To - /// prevent this we take advantage of RealProxy's ability of - /// "dressing up" like other classes and hence successfully respond to QIs - /// for interfaces that it does not really support( it is OK to say - /// "I implement this interface" for event sinks only since the common - /// practice is to use IDispatch.Invoke when calling into event sinks). - /// - internal sealed class ComEventSinkProxy : RealProxy - { - private Guid _sinkIid; - private ComEventSink _sink; - private static readonly MethodInfo s_methodInfoInvokeMember = typeof(ComEventSink).GetMethod("InvokeMember", BindingFlags.Instance | BindingFlags.Public); - - #region ctors - - private ComEventSinkProxy() - { - } - - public ComEventSinkProxy(ComEventSink sink, Guid sinkIid) - : base(typeof(ComEventSink)) - { - _sink = sink; - _sinkIid = sinkIid; - } - - #endregion - - #region Base Class Overrides - - public override IntPtr SupportsInterface(ref Guid iid) - { - // if the iid is the sink iid, we ask the base class for an rcw to IDispatch - if (iid == _sinkIid) - { - IntPtr retVal = IntPtr.Zero; - retVal = Marshal.GetIDispatchForObject(_sink); - return retVal; - } - - return base.SupportsInterface(ref iid); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] - public override IMessage Invoke(IMessage msg) - { - // Only know how to handle method calls (property and fields accessors count as methods) - IMethodCallMessage methodCallMessage = msg as IMethodCallMessage; - if (methodCallMessage == null) - throw new NotSupportedException(); - - // ComEventSink.InvokeMember is handled specially. - // The reason we need to do that is due to how namedParameters arg (7th element in the IMethodCallMessage.Args array) - // is marshalled when called through RealProxy.Invoke. - // In RealProxy.Invoke namedParameters is typed as object[], while InvokeMember expects it to be string[]. - // If we simply let this call go through (with RemotingServices.ExecuteMessage) - // we get an InvalidCastException when Remoting tries to pass namedParameters (of type object[]) - // to InvokeMember (which expects namedParameters to be string[]). - // Since we don't use namedParameters in ComEventSink.InvokeMember - we simply ignore it here - // and pass-in null. - MethodInfo methodInfo = (MethodInfo)methodCallMessage.MethodBase; - if (methodInfo == s_methodInfoInvokeMember) - { - object retVal = null; - - try - { - // InvokeMember(string name, BindingFlags bindingFlags, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) - retVal = ((IReflect)_sink).InvokeMember( - /*name*/ methodCallMessage.Args[0] as string, - /*bindingFlags*/ (BindingFlags)methodCallMessage.Args[1], - /*binder*/ methodCallMessage.Args[2] as Binder, - /*target*/ null, - /*args*/ methodCallMessage.Args[4] as object[], - /*modifiers*/ methodCallMessage.Args[5] as ParameterModifier[], - /*culture*/ methodCallMessage.Args[6] as CultureInfo, - /*namedParameters*/ null); - } - catch (Exception ex) - { - return new ReturnMessage(ex.InnerException, methodCallMessage); - } - - return new ReturnMessage(retVal, methodCallMessage.Args, methodCallMessage.ArgCount, null, methodCallMessage); - } - - return RemotingServices.ExecuteMessage(_sink, methodCallMessage); - } - - #endregion - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventSinksContainer.cs b/src/System.Management.Automation/engine/ComInterop/ComEventSinksContainer.cs index 029068323f6..52a11e7f041 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComEventSinksContainer.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComEventSinksContainer.cs @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Management.Automation.InteropServices; namespace System.Management.Automation.ComInterop { @@ -13,7 +13,7 @@ namespace System.Management.Automation.ComInterop /// This list is usually attached as a custom data for RCW object and /// is finalized whenever RCW is finalized. /// - internal class ComEventSinksContainer : List, IDisposable + internal class ComEventSinksContainer : List, IDisposable { private ComEventSinksContainer() { @@ -21,14 +21,10 @@ private ComEventSinksContainer() private static readonly object s_comObjectEventSinksKey = new object(); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")] public static ComEventSinksContainer FromRuntimeCallableWrapper(object rcw, bool createIfNotFound) { - // !!! Marshal.Get/SetComObjectData has a LinkDemand for UnmanagedCode which will turn into - // a full demand. We need to avoid this by making this method SecurityCritical object data = Marshal.GetComObjectData(rcw, s_comObjectEventSinksKey); - if (data != null || createIfNotFound == false) + if (data != null || !createIfNotFound) { return (ComEventSinksContainer)data; } @@ -63,9 +59,9 @@ public void Dispose() private void DisposeAll() { - foreach (ComEventSink sink in this) + foreach (ComEventsSink sink in this) { - sink.Dispose(); + ComEventsSink.RemoveAll(sink); } } @@ -75,6 +71,3 @@ private void DisposeAll() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs b/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs new file mode 100644 index 00000000000..0d63276a3ea --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/ComEventsSink.Extended.cs @@ -0,0 +1,105 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +using System.Management.Automation.ComInterop; + +namespace System.Management.Automation.InteropServices +{ + internal partial class ComEventsSink + { + private void Initialize(object rcw, Guid iid) + { + _iidSourceItf = iid; + Advise(rcw); + } + + public void AddHandler(int dispid, object func) + { + ComEventsMethod method = FindMethod(dispid); + method ??= AddMethod(dispid); + + if (func is Delegate d) + { + method.AddDelegate(d); + } + else + { + method.AddDelegate(new SplatCallSite.InvokeDelegate(new SplatCallSite(func).Invoke), wrapArgs: true); + } + } + + public void RemoveHandler(int dispid, object func) + { + ComEventsMethod sinkEntry = FindMethod(dispid); + if (sinkEntry == null) + { + return; + } + + if (func is Delegate d) + { + sinkEntry.RemoveDelegate(d); + } + else + { + // Remove the delegate from multicast delegate chain. + // We will need to find the delegate that corresponds + // to the func handler we want to remove. This will be + // easy since we Target property of the delegate object + // is a ComEventCallContext object. + sinkEntry.RemoveDelegates(d => d.Target is SplatCallSite callContext && callContext._callable.Equals(func)); + } + + // If the delegates chain is empty - we can remove + // corresponding ComEvenSinkEntry + if (sinkEntry.Empty) + RemoveMethod(sinkEntry); + + if (_methods == null || _methods.Empty) + { + Unadvise(); + _iidSourceItf = Guid.Empty; + } + } + + public static ComEventsSink FromRuntimeCallableWrapper(object rcw, Guid sourceIid, bool createIfNotFound) + { + List comEventSinks = ComEventSinksContainer.FromRuntimeCallableWrapper(rcw, createIfNotFound); + if (comEventSinks == null) + { + return null; + } + + ComEventsSink comEventSink = null; + lock (comEventSinks) + { + foreach (ComEventsSink sink in comEventSinks) + { + if (sink._iidSourceItf == sourceIid) + { + comEventSink = sink; + break; + } + + if (sink._iidSourceItf == Guid.Empty) + { + // we found a ComEventSink object that + // was previously disposed. Now we will reuse it. + sink.Initialize(rcw, sourceIid); + comEventSink = sink; + } + } + + if (comEventSink == null && createIfNotFound) + { + comEventSink = new ComEventsSink(rcw, sourceIid); + comEventSinks.Add(comEventSink); + } + } + + return comEventSink; + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs index 44765fd39e9..8a6e81f0800 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComFallbackMetaObject.cs @@ -1,17 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Dynamic; - -//using Microsoft.Scripting.Utils; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -32,26 +23,31 @@ internal ComFallbackMetaObject(Expression expression, BindingRestrictions restri public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { + Requires.NotNull(binder); return binder.FallbackGetIndex(UnwrapSelf(), indexes); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { + Requires.NotNull(binder); return binder.FallbackSetIndex(UnwrapSelf(), indexes, value); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { + Requires.NotNull(binder); return binder.FallbackGetMember(UnwrapSelf()); } public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { + Requires.NotNull(binder); return binder.FallbackInvokeMember(UnwrapSelf(), args); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { + Requires.NotNull(binder); return binder.FallbackSetMember(UnwrapSelf(), value); } @@ -75,6 +71,3 @@ internal ComUnwrappedMetaObject(Expression expression, BindingRestrictions restr } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComHresults.cs b/src/System.Management.Automation/engine/ComInterop/ComHresults.cs index 3fd90e9a733..9d2455bc782 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComHresults.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComHresults.cs @@ -1,7 +1,5 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. namespace System.Management.Automation.ComInterop { @@ -9,8 +7,6 @@ internal static class ComHresults { internal const int S_OK = 0; - internal const int CONNECT_E_NOCONNECTION = unchecked((int)0x80040200); - internal const int DISP_E_UNKNOWNINTERFACE = unchecked((int)0x80020001); internal const int DISP_E_MEMBERNOTFOUND = unchecked((int)0x80020003); internal const int DISP_E_PARAMNOTFOUND = unchecked((int)0x80020004); @@ -31,7 +27,10 @@ internal static class ComHresults internal const int E_FAIL = unchecked((int)0x80004005); internal const int TYPE_E_LIBNOTREGISTERED = unchecked((int)0x8002801D); + + internal static bool IsSuccess(int hresult) + { + return hresult >= 0; + } } } -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComInterop.cs b/src/System.Management.Automation/engine/ComInterop/ComInterop.cs index 21a334a1991..4024163619a 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComInterop.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComInterop.cs @@ -1,28 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Runtime.InteropServices; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop { - [ - ComImport, - InterfaceType(ComInterfaceType.InterfaceIsIDispatch), - Guid("00020400-0000-0000-C000-000000000046") - ] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces")] - internal interface IDispatchForReflection - { - } - - [ - ComImport, - InterfaceType(ComInterfaceType.InterfaceIsIUnknown), - Guid("00020400-0000-0000-C000-000000000046"), - ] + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("00020400-0000-0000-C000-000000000046")] internal interface IDispatch { [PreserveSig] @@ -49,36 +36,23 @@ int TryInvoke( int lcid, ComTypes.INVOKEKIND wFlags, ref ComTypes.DISPPARAMS pDispParams, - out object VarResult, - out ComTypes.EXCEPINFO pExcepInfo, - out uint puArgErr); + IntPtr VarResult, + IntPtr pExcepInfo, + IntPtr puArgErr); } - /// - /// Layout of the IDispatch vtable. - /// - internal enum IDispatchMethodIndices + [ComImport] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [Guid("B196B283-BAB4-101A-B69C-00AA00341D07")] + internal interface IProvideClassInfo { - IUnknown_QueryInterface, - IUnknown_AddRef, - IUnknown_Release, - - IDispatch_GetTypeInfoCount, - IDispatch_GetTypeInfo, - IDispatch_GetIDsOfNames, - IDispatch_Invoke + void GetClassInfo(out IntPtr info); } - [ - ComImport, - InterfaceType(ComInterfaceType.InterfaceIsIUnknown), - Guid("B196B283-BAB4-101A-B69C-00AA00341D07") - ] - internal interface IProvideClassInfo + internal static class ComDispIds { - void GetClassInfo(out IntPtr info); + internal const int DISPID_VALUE = 0; + internal const int DISPID_PROPERTYPUT = -3; + internal const int DISPID_NEWENUM = -4; } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComInvokeAction.cs b/src/System.Management.Automation/engine/ComInterop/ComInvokeAction.cs index 9375a1f977e..70c94996c09 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComInvokeAction.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComInvokeAction.cs @@ -1,16 +1,11 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Dynamic; +using System; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Dynamic; +using System.Linq.Expressions; using System.Runtime.CompilerServices; namespace System.Management.Automation.ComInterop @@ -25,20 +20,9 @@ internal ComInvokeAction(CallInfo callInfo) { } - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public override bool Equals(object obj) - { - return base.Equals(obj as ComInvokeAction); - } - public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion) { - DynamicMetaObject res; - if (ComBinder.TryBindInvoke(this, target, args, out res)) + if (ComBinder.TryBindInvoke(this, target, args, out DynamicMetaObject res)) { return res; } @@ -48,7 +32,8 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam Expression.New( typeof(NotSupportedException).GetConstructor(new[] { typeof(string) }), Expression.Constant(ParserStrings.CannotCall) - ) + ), + typeof(object) ), target.Restrictions.Merge(BindingRestrictions.Combine(args)) ); @@ -81,7 +66,6 @@ public override Expression Bind(object[] args, ReadOnlyCollection list, ParameterExpression var) { - if (var != null) list.Add(var); + if (var != null) + { + list.Add(var); + } } private Expression CreateScope(Expression expression) @@ -204,7 +198,6 @@ private Expression GenerateTryBlock() ParameterExpression hresult = Expression.Variable(typeof(int), "hresult"); List tryStatements = new List(); - Expression expr; if (_keywordArgNames.Length > 0) { @@ -214,9 +207,9 @@ private Expression GenerateTryBlock() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.rgdispidNamedArgs)) ), - Expression.Call(typeof(UnsafeMethods).GetMethod("GetIdsOfNamedParameters"), + Expression.Call(typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.GetIdsOfNamedParameters)), DispatchObjectVariable, Expression.Constant(names), DispIdVariable, @@ -254,7 +247,6 @@ private Expression GenerateTryBlock() // Positional arguments are in reverse order at the tail of rgArgs variantIndex = reverseIndex; } - VariantBuilder variantBuilder = _varEnumSelector.VariantBuilders[i]; Expression marshal = variantBuilder.InitializeArgumentVariant( @@ -291,7 +283,7 @@ private Expression GenerateTryBlock() } MethodCallExpression invoke = Expression.Call( - typeof(UnsafeMethods).GetMethod("IDispatchInvoke"), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.IDispatchInvoke)), DispatchPointerVariable, DispIdVariable, Expression.Constant(invokeKind), @@ -301,11 +293,11 @@ private Expression GenerateTryBlock() argErr ); - expr = Expression.Assign(hresult, invoke); + Expression expr = Expression.Assign(hresult, invoke); tryStatements.Add(expr); // - // ComRuntimeHelpers.CheckThrowException(int hresult, ref ExcepInfo excepInfo, ComMethodDesc method, object args, uint argErr) + // ComRuntimeHelpers.CheckThrowException(int hresult, ref ExcepInfo excepInfo, ComMethodDesc method, object[] args, uint argErr) List args = new List(); foreach (Expression parameter in parameters) { @@ -313,7 +305,7 @@ private Expression GenerateTryBlock() } expr = Expression.Call( - typeof(ComRuntimeHelpers).GetMethod("CheckThrowException"), + typeof(ComRuntimeHelpers).GetMethod(nameof(ComRuntimeHelpers.CheckThrowException)), hresult, excepInfo, Expression.Constant(_methodDesc, typeof(ComMethodDesc)), @@ -328,7 +320,7 @@ private Expression GenerateTryBlock() Expression invokeResultObject = Expression.Call( InvokeResultVariable, - typeof(Variant).GetMethod("ToObject")); + typeof(Variant).GetMethod(nameof(Variant.ToObject))); VariantBuilder[] variants = _varEnumSelector.VariantBuilders; @@ -351,17 +343,16 @@ private Expression GenerateTryBlock() private Expression GenerateFinallyBlock() { - List finallyStatements = new List(); - - // - // UnsafeMethods.IUnknownRelease(dispatchPointer); - // - finallyStatements.Add( + List finallyStatements = new List + { + // + // UnsafeMethods.IUnknownRelease(dispatchPointer); + // Expression.Call( - typeof(UnsafeMethods).GetMethod("IUnknownRelease"), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.IUnknownRelease)), DispatchPointerVariable ) - ); + }; // // Clear memory allocated for marshalling @@ -382,7 +373,7 @@ private Expression GenerateFinallyBlock() finallyStatements.Add( Expression.Call( InvokeResultVariable, - typeof(Variant).GetMethod("Clear") + typeof(Variant).GetMethod(nameof(Variant.Clear)) ) ); @@ -394,7 +385,7 @@ private Expression GenerateFinallyBlock() finallyStatements.Add( Expression.Call( DispIdsOfKeywordArgsPinnedVariable, - typeof(GCHandle).GetMethod("Free") + typeof(GCHandle).GetMethod(nameof(GCHandle.Free)) ) ); } @@ -404,24 +395,23 @@ 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() { Debug.Assert(_varEnumSelector.VariantBuilders.Length == _totalExplicitArgs); - List exprs = new List(); - - // - // _dispId = ((DispCallable)this).ComMethodDesc.DispId; - // - exprs.Add( + List exprs = new List + { + // + // _dispId = ((DispCallable)this).ComMethodDesc.DispId; + // Expression.Assign( DispIdVariable, - Expression.Property(_method, typeof(ComMethodDesc).GetProperty("DispId")) + Expression.Property(_method, typeof(ComMethodDesc).GetProperty(nameof(ComMethodDesc.DispId))) ) - ); + }; // // _dispParams.rgvararg = RuntimeHelpers.UnsafeMethods.ConvertVariantByrefToPtr(ref _paramVariants._element0) @@ -432,10 +422,10 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("rgvarg") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.rgvarg)) ), Expression.Call( - typeof(UnsafeMethods).GetMethod("ConvertVariantByrefToPtr"), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.ConvertVariantByrefToPtr)), VariantArray.GetStructField(ParamVariantsVariable, 0) ) ) @@ -449,7 +439,7 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("cArgs") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.cArgs)) ), Expression.Constant(_totalExplicitArgs) ) @@ -465,7 +455,7 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.cNamedArgs)) ), Expression.Constant(1) ) @@ -482,10 +472,10 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("rgdispidNamedArgs") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.rgdispidNamedArgs)) ), Expression.Call( - typeof(UnsafeMethods).GetMethod("ConvertInt32ByrefToPtr"), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.ConvertInt32ByrefToPtr)), PropertyPutDispIdVariable ) ) @@ -500,7 +490,7 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( Expression.Field( DispParamsVariable, - typeof(ComTypes.DISPPARAMS).GetField("cNamedArgs") + typeof(ComTypes.DISPPARAMS).GetField(nameof(ComTypes.DISPPARAMS.cNamedArgs)) ), Expression.Constant(_keywordArgNames.Length) ) @@ -518,7 +508,7 @@ private Expression MakeIDispatchInvokeTarget() Expression.Assign( DispatchPointerVariable, Expression.Call( - typeof(Marshal).GetMethod("GetIDispatchForObject"), + typeof(Marshal).GetMethod(nameof(Marshal.GetIDispatchForObject)), DispatchObjectVariable ) ) @@ -531,7 +521,7 @@ private Expression MakeIDispatchInvokeTarget() exprs.Add(ReturnValueVariable); var vars = new List(); - foreach (var variant in _varEnumSelector.VariantBuilders) + foreach (VariantBuilder variant in _varEnumSelector.VariantBuilders) { if (variant.TempVariable != null) { @@ -569,11 +559,7 @@ private Expression[] MakeArgumentExpressions() { res[copy++] = _args[i].Expression; } - return res; } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs index bf16a2f2c5d..39c9d1dbe05 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComMetaObject.cs @@ -1,15 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Dynamic; - -//using Microsoft.Scripting.Utils; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -23,31 +16,37 @@ internal ComMetaObject(Expression expression, BindingRestrictions restrictions, public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { + Requires.NotNull(binder); return binder.Defer(args.AddFirst(WrapSelf())); } public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { + Requires.NotNull(binder); return binder.Defer(args.AddFirst(WrapSelf())); } public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { + Requires.NotNull(binder); return binder.Defer(WrapSelf()); } public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { + Requires.NotNull(binder); return binder.Defer(WrapSelf(), value); } public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { + Requires.NotNull(binder); return binder.Defer(WrapSelf(), indexes); } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { + Requires.NotNull(binder); return binder.Defer(WrapSelf(), indexes.AddLast(value)); } @@ -57,7 +56,7 @@ private DynamicMetaObject WrapSelf() ComObject.RcwToComObject(Expression), BindingRestrictions.GetExpressionRestriction( Expression.Call( - typeof(ComObject).GetMethod("IsComObject", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic), + typeof(ComBinder).GetMethod(nameof(ComBinder.IsComObject), System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public), Helpers.Convert(Expression, typeof(object)) ) ) @@ -65,6 +64,3 @@ private DynamicMetaObject WrapSelf() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs index 79ba5d8f2a4..45932e1598a 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComMethodDesc.cs @@ -1,7 +1,5 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; using System.Runtime.InteropServices.ComTypes; @@ -10,7 +8,6 @@ namespace System.Management.Automation.ComInterop { internal class ComMethodDesc { - private readonly string _name; internal readonly INVOKEKIND InvokeKind; private ComMethodDesc(int dispId) @@ -22,7 +19,7 @@ internal ComMethodDesc(string name, int dispId) : this(dispId) { // no ITypeInfo constructor - _name = name; + Name = name; } internal ComMethodDesc(string name, int dispId, INVOKEKIND invkind) @@ -36,9 +33,8 @@ internal ComMethodDesc(ITypeInfo typeInfo, FUNCDESC funcDesc) { InvokeKind = funcDesc.invkind; - int cNames; string[] rgNames = new string[1 + funcDesc.cParams]; - typeInfo.GetNames(DispId, rgNames, rgNames.Length, out cNames); + typeInfo.GetNames(DispId, rgNames, rgNames.Length, out int cNames); bool skipLast = false; if (IsPropertyPut && rgNames[rgNames.Length - 1] == null) @@ -47,24 +43,15 @@ internal ComMethodDesc(ITypeInfo typeInfo, FUNCDESC funcDesc) cNames++; skipLast = true; } - Debug.Assert(cNames == rgNames.Length); - _name = rgNames[0]; + Name = rgNames[0]; ParamCount = funcDesc.cParams; - ReturnType = ComUtil.GetTypeFromTypeDesc(funcDesc.elemdescFunc.tdesc); ParameterInformation = ComUtil.GetParameterInformation(funcDesc, skipLast); } - public string Name - { - get - { - Debug.Assert(_name != null); - return _name; - } - } + public string Name { get; } public int DispId { get; } @@ -80,13 +67,13 @@ public bool IsDataMember { get { - // must be regular get + //must be regular get if (!IsPropertyGet || DispId == ComDispIds.DISPID_NEWENUM) { return false; } - // must have no parameters + //must have no parameters return ParamCount == 0; } } @@ -110,6 +97,7 @@ public bool IsPropertyPutRef internal int ParamCount { get; } public Type ReturnType { get; set; } + public Type InputType { get; set; } public ParameterInformation[] ParameterInformation @@ -119,6 +107,3 @@ public ParameterInformation[] ParameterInformation } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComObject.cs b/src/System.Management.Automation/engine/ComInterop/ComObject.cs index 65ac238a06c..52c4cce61e0 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComObject.cs @@ -1,50 +1,39 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections.Generic; using System.Diagnostics; +using System.Dynamic; +using System.Linq.Expressions; using System.Reflection; using System.Runtime.InteropServices; -using System.Dynamic; namespace System.Management.Automation.ComInterop { /// - /// This is a helper class for runtime-callable-wrappers of COM instances. We create one instance of this type - /// for every generic RCW instance. + /// The ComObject class wraps a runtime-callable-wrapper and enables it to be used with the Dynamic Language Runtime and the C# dynamic keyword. /// internal class ComObject : IDynamicMetaObjectProvider { internal ComObject(object rcw) { - Debug.Assert(ComObject.IsComObject(rcw)); + Debug.Assert(ComBinder.IsComObject(rcw)); RuntimeCallableWrapper = rcw; } - /// - /// The runtime-callable wrapper. - /// internal object RuntimeCallableWrapper { get; } private static readonly object s_comObjectInfoKey = new object(); /// - /// This is the factory method to get the ComObject corresponding to an RCW. + /// Gets a that wraps the runtime-callable-wrapper, or creates one if none currently exists. /// /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] public static ComObject ObjectToComObject(object rcw) { - Debug.Assert(ComObject.IsComObject(rcw)); + Debug.Assert(ComBinder.IsComObject(rcw)); - // Marshal.Get/SetComObjectData has a LinkDemand for UnmanagedCode which will turn into - // a full demand. We could avoid this by making this method SecurityCritical object data = Marshal.GetComObjectData(rcw, s_comObjectInfoKey); if (data != null) { @@ -72,11 +61,11 @@ public static ComObject ObjectToComObject(object rcw) // Expression that unwraps ComObject internal static MemberExpression RcwFromComObject(Expression comObject) { - Debug.Assert(comObject != null && typeof(ComObject).IsAssignableFrom(comObject.Type), "must be ComObject"); + Debug.Assert(comObject != null && (typeof(ComObject).IsAssignableFrom(comObject.Type) || comObject.Type == typeof(object)), "must be ComObject"); return Expression.Property( Helpers.Convert(comObject, typeof(ComObject)), - typeof(ComObject).GetProperty("RuntimeCallableWrapper", BindingFlags.NonPublic | BindingFlags.Instance) + typeof(ComObject).GetProperty(nameof(RuntimeCallableWrapper), BindingFlags.NonPublic | BindingFlags.Instance) ); } @@ -84,15 +73,14 @@ internal static MemberExpression RcwFromComObject(Expression comObject) internal static MethodCallExpression RcwToComObject(Expression rcw) { return Expression.Call( - typeof(ComObject).GetMethod("ObjectToComObject"), + typeof(ComObject).GetMethod(nameof(ObjectToComObject)), Helpers.Convert(rcw, typeof(object)) ); } private static ComObject CreateComObject(object rcw) { - IDispatch dispatchObject = rcw as IDispatch; - if (dispatchObject != null) + if (rcw is IDispatch dispatchObject) { // We can do method invocations on IDispatch objects return new IDispatchComObject(dispatchObject); @@ -116,16 +104,5 @@ DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) { return new ComFallbackMetaObject(parameter, BindingRestrictions.Empty, this); } - - private static readonly Type s_comObjectType = typeof(object).Assembly.GetType("System.__ComObject"); - - internal static bool IsComObject(object obj) - { - // we can't use System.Runtime.InteropServices.Marshal.IsComObject(obj) since it doesn't work in partial trust - return obj != null && s_comObjectType.IsAssignableFrom(obj.GetType()); - } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComParamDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComParamDesc.cs deleted file mode 100644 index e98f23cdc93..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComParamDesc.cs +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -using System.Runtime.InteropServices.ComTypes; -using System.Text; -using Marshal = System.Runtime.InteropServices.Marshal; -using VarEnum = System.Runtime.InteropServices.VarEnum; - -namespace System.Management.Automation.ComInterop -{ - /// - /// The parameter description of a method defined in a type library. - /// - internal class ComParamDesc - { - #region private fields - - private readonly VarEnum _vt; - private readonly string _name; - - #endregion - - #region ctor - - /// - /// Creates a representation for the parameter of a COM method. - /// - internal ComParamDesc(ref ELEMDESC elemDesc, string name) - { - // Ensure _defaultValue is set to DBNull.Value regardless of whether or not the - // default value is extracted from the parameter description. Failure to do so - // yields a runtime exception in the ToString() function. - DefaultValue = DBNull.Value; - - if (!string.IsNullOrEmpty(name)) - { - // This is a parameter, not a return value - IsOut = (elemDesc.desc.paramdesc.wParamFlags & PARAMFLAG.PARAMFLAG_FOUT) != 0; - IsOptional = (elemDesc.desc.paramdesc.wParamFlags & PARAMFLAG.PARAMFLAG_FOPT) != 0; - // TODO: The PARAMDESCEX struct has a memory issue that needs to be resolved. For now, we ignore it. - // _defaultValue = PARAMDESCEX.GetDefaultValue(ref elemDesc.desc.paramdesc); - } - - _name = name; - _vt = (VarEnum)elemDesc.tdesc.vt; - TYPEDESC typeDesc = elemDesc.tdesc; - while (true) - { - if (_vt == VarEnum.VT_PTR) - { - ByReference = true; - } - else if (_vt == VarEnum.VT_ARRAY) - { - IsArray = true; - } - else - { - break; - } - - TYPEDESC childTypeDesc = (TYPEDESC)Marshal.PtrToStructure(typeDesc.lpValue, typeof(TYPEDESC)); - _vt = (VarEnum)childTypeDesc.vt; - typeDesc = childTypeDesc; - } - - VarEnum vtWithoutByref = _vt; - if ((_vt & VarEnum.VT_BYREF) != 0) - { - vtWithoutByref = (_vt & ~VarEnum.VT_BYREF); - ByReference = true; - } - - ParameterType = VarEnumSelector.GetTypeForVarEnum(vtWithoutByref); - } - - /// - /// Creates a representation for the return value of a COM method - /// TODO: Return values should be represented by a different type. - /// - internal ComParamDesc(ref ELEMDESC elemDesc) - : this(ref elemDesc, string.Empty) - { - } - - public override string ToString() - { - StringBuilder result = new StringBuilder(); - if (IsOptional) - { - result.Append("[Optional] "); - } - - if (IsOut) - { - result.Append("[out]"); - } - - result.Append(ParameterType.Name); - - if (IsArray) - { - result.Append("[]"); - } - - if (ByReference) - { - result.Append("&"); - } - - result.Append(" "); - result.Append(_name); - - if (DefaultValue != DBNull.Value) - { - result.Append("="); - result.Append(DefaultValue.ToString()); - } - - return result.ToString(); - } - - #endregion - - #region properties - - public bool IsOut { get; } - - public bool IsOptional { get; } - - public bool ByReference { get; } - - public bool IsArray { get; } - - public Type ParameterType { get; } - - /// - /// DBNull.Value if there is no default value. - /// - internal object DefaultValue { get; } - - #endregion - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs b/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs index c421fe09ac9..5bd086874a6 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComRuntimeHelpers.cs @@ -1,30 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Management.Automation.InteropServices; using System.Runtime.InteropServices; using System.Security; -using System.Security.Permissions; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop { internal static class ComRuntimeHelpers { - [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference", MessageId = "1#")] public static void CheckThrowException(int hresult, ref ExcepInfo excepInfo, ComMethodDesc method, object[] args, uint argErr) { - if (Utils.Succeeded(hresult)) + if (ComHresults.IsSuccess(hresult)) { return; } - Exception parameterException = null; switch (hresult) @@ -32,13 +28,12 @@ public static void CheckThrowException(int hresult, ref ExcepInfo excepInfo, Com case ComHresults.DISP_E_BADPARAMCOUNT: // The number of elements provided to DISPPARAMS is different from the number of arguments // accepted by the method or property. - parameterException = Error.DispBadParamCount(method.Name, args.Length - 1); ThrowWrappedInvocationException(method, parameterException); break; case ComHresults.DISP_E_BADVARTYPE: - // One of the arguments in rgvarg is not a valid variant type. + //One of the arguments in rgvarg is not a valid variant type. break; case ComHresults.DISP_E_EXCEPTION: @@ -60,6 +55,8 @@ public static void CheckThrowException(int hresult, ref ExcepInfo excepInfo, Com throw Error.DispOverflow(method.Name); case ComHresults.DISP_E_PARAMNOTFOUND: + // One of the parameter DISPIDs does not correspond to a parameter on the method. In this case, + // puArgErr should be set to the first argument that contains the error. break; case ComHresults.DISP_E_TYPEMISMATCH: @@ -147,36 +144,25 @@ private static void ThrowWrappedInvocationException(ComMethodDesc method, Except internal static void GetInfoFromType(ComTypes.ITypeInfo typeInfo, out string name, out string documentation) { - int dwHelpContext; - string strHelpFile; - - typeInfo.GetDocumentation(-1, out name, out documentation, out dwHelpContext, out strHelpFile); + typeInfo.GetDocumentation(-1, out name, out documentation, out int _, out string _); } internal static string GetNameOfMethod(ComTypes.ITypeInfo typeInfo, int memid) { - int cNames; string[] rgNames = new string[1]; - typeInfo.GetNames(memid, rgNames, 1, out cNames); + typeInfo.GetNames(memid, rgNames, 1, out int _); return rgNames[0]; } internal static string GetNameOfLib(ComTypes.ITypeLib typeLib) { - string name; - string strDocString; - int dwHelpContext; - string strHelpFile; - - typeLib.GetDocumentation(-1, out name, out strDocString, out dwHelpContext, out strHelpFile); + typeLib.GetDocumentation(-1, out string name, out string _, out int _, out string _); return name; } internal static string GetNameOfType(ComTypes.ITypeInfo typeInfo) { - string name; - string documentation; - GetInfoFromType(typeInfo, out name, out documentation); + GetInfoFromType(typeInfo, out string name, out string _); return name; } @@ -184,19 +170,19 @@ internal static string GetNameOfType(ComTypes.ITypeInfo typeInfo) /// /// Look for typeinfo using IDispatch.GetTypeInfo. /// - /// - /// + /// IDispatch object + /// /// Some COM objects just dont expose typeinfo. In these cases, this method will return null. /// Some COM objects do intend to expose typeinfo, but may not be able to do so if the type-library is not properly - /// registered. This will be considered as acceptable or as an error condition depending on throwIfMissingExpectedTypeInfo - /// - internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch, bool throwIfMissingExpectedTypeInfo) + /// registered. This will be considered as acceptable or as an error condition depending on throwIfMissingExpectedTypeInfo + /// + /// Type info + internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch) { - uint typeCount; - int hresult = dispatch.TryGetTypeInfoCount(out typeCount); - - if ((hresult == ComHresults.E_NOTIMPL) || (hresult == ComHresults.E_NOINTERFACE)) + int hresult = dispatch.TryGetTypeInfoCount(out uint typeCount); + if (hresult == ComHresults.E_NOTIMPL || hresult == ComHresults.E_NOINTERFACE) { + // Allow the dynamic binding to continue using the original binder. return null; } else @@ -210,23 +196,28 @@ internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch, return null; } - IntPtr typeInfoPtr = IntPtr.Zero; - + IntPtr typeInfoPtr; hresult = dispatch.TryGetTypeInfo(0, 0, out typeInfoPtr); - if (!Utils.Succeeded(hresult)) + if (!ComHresults.IsSuccess(hresult)) { - CheckIfMissingTypeInfoIsExpected(hresult, throwIfMissingExpectedTypeInfo); - return null; - } - - if (typeInfoPtr == IntPtr.Zero) - { // be defensive against components that return IntPtr.Zero - if (throwIfMissingExpectedTypeInfo) + // Word.Basic always returns this because of an incorrect implementation of IDispatch.GetTypeInfo + // Any implementation that returns E_NOINTERFACE is likely to do so in all environments + if (hresult == ComHresults.E_NOINTERFACE) { - Marshal.ThrowExceptionForHR(ComHresults.E_FAIL); + return null; } - return null; + // This assert is potentially over-restrictive since COM components can behave in quite unexpected ways. + // However, asserting the common expected cases ensures that we find out about the unexpected scenarios, and + // can investigate the scenarios to ensure that there is no bug in our own code. + Debug.Assert(hresult == ComHresults.TYPE_E_LIBNOTREGISTERED); + + Marshal.ThrowExceptionForHR(hresult); + } + + if (typeInfoPtr == IntPtr.Zero) + { + Marshal.ThrowExceptionForHR(ComHresults.E_FAIL); } ComTypes.ITypeInfo typeInfo = null; @@ -242,41 +233,9 @@ internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch, return typeInfo; } - /// - /// This method should be called when typeinfo is not available for an object. The function - /// will check if the typeinfo is expected to be missing. This can include error cases where - /// the same error is guaranteed to happen all the time, on all machines, under all circumstances. - /// In such cases, we just have to operate without the typeinfo. - /// - /// However, if accessing the typeinfo is failing in a transient way, we might want to throw - /// an exception so that we will eagerly predictably indicate the problem. - /// - private static void CheckIfMissingTypeInfoIsExpected(int hresult, bool throwIfMissingExpectedTypeInfo) - { - Debug.Assert(!Utils.Succeeded(hresult)); - - // Word.Basic always returns this because of an incorrect implementation of IDispatch.GetTypeInfo - // Any implementation that returns E_NOINTERFACE is likely to do so in all environments - if (hresult == ComHresults.E_NOINTERFACE) - { - return; - } - - // This assert is potentially over-restrictive since COM components can behave in quite unexpected ways. - // However, asserting the common expected cases ensures that we find out about the unexpected scenarios, and - // can investigate the scenarios to ensure that there is no bug in our own code. - Debug.Assert(hresult == ComHresults.TYPE_E_LIBNOTREGISTERED); - - if (throwIfMissingExpectedTypeInfo) - { - Marshal.ThrowExceptionForHR(hresult); - } - } - - [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] internal static ComTypes.TYPEATTR GetTypeAttrForTypeInfo(ComTypes.ITypeInfo typeInfo) { - IntPtr pAttrs = IntPtr.Zero; + IntPtr pAttrs; typeInfo.GetTypeAttr(out pAttrs); // GetTypeAttr should never return null, this is just to be safe @@ -295,10 +254,9 @@ internal static ComTypes.TYPEATTR GetTypeAttrForTypeInfo(ComTypes.ITypeInfo type } } - [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] internal static ComTypes.TYPELIBATTR GetTypeAttrForTypeLib(ComTypes.ITypeLib typeLib) { - IntPtr pAttrs = IntPtr.Zero; + IntPtr pAttrs; typeLib.GetLibAttr(out pAttrs); // GetTypeAttr should never return null, this is just to be safe @@ -333,132 +291,28 @@ public static DispCallable CreateDispCallable(IDispatchComObject dispatch, ComMe /// Callers of these methods need to use them extremely carefully as incorrect use could cause GC-holes /// and other problems. /// + /// internal static class UnsafeMethods { - [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.None)] - [System.Runtime.Versioning.ResourceConsumption(System.Runtime.Versioning.ResourceScope.Process, System.Runtime.Versioning.ResourceScope.Process)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] // TODO: fix - [DllImport("oleaut32.dll", PreserveSig = false)] - internal static extern void VariantClear(IntPtr variant); - - [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.Machine)] - [System.Runtime.Versioning.ResourceConsumption(System.Runtime.Versioning.ResourceScope.Machine, System.Runtime.Versioning.ResourceScope.Machine)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1060:MovePInvokesToNativeMethodsClass")] // TODO: fix - [DllImport("oleaut32.dll", PreserveSig = false)] - internal static extern ComTypes.ITypeLib LoadRegTypeLib(ref Guid clsid, short majorVersion, short minorVersion, int lcid); - #region public members - private static readonly MethodInfo s_convertByrefToPtr = Create_ConvertByrefToPtr(); - - public delegate IntPtr ConvertByrefToPtrDelegate(ref T value); - - private static readonly ConvertByrefToPtrDelegate s_convertVariantByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(Variant))); - - private static MethodInfo Create_ConvertByrefToPtr() + public static unsafe IntPtr ConvertInt32ByrefToPtr(ref int value) { - // We dont use AssemblyGen.DefineMethod since that can create a anonymously-hosted DynamicMethod which cannot contain unverifiable code. - var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("ComSnippets"), AssemblyBuilderAccess.Run); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("ComSnippets"); - var type = moduleBuilder.DefineType("Type$ConvertByrefToPtr", TypeAttributes.Public); - - Type[] paramTypes = new Type[] { typeof(Variant).MakeByRefType() }; - MethodBuilder mb = type.DefineMethod("ConvertByrefToPtr", MethodAttributes.Public | MethodAttributes.Static, typeof(IntPtr), paramTypes); - GenericTypeParameterBuilder[] typeParams = mb.DefineGenericParameters("T"); - typeParams[0].SetGenericParameterAttributes(GenericParameterAttributes.NotNullableValueTypeConstraint); - mb.SetSignature(typeof(IntPtr), null, null, new Type[] { typeParams[0].MakeByRefType() }, null, null); - - ILGenerator method = mb.GetILGenerator(); - - method.Emit(OpCodes.Ldarg_0); - method.Emit(OpCodes.Conv_I); - method.Emit(OpCodes.Ret); - - return type.CreateType().GetMethod("ConvertByrefToPtr"); + return (IntPtr)System.Runtime.CompilerServices.Unsafe.AsPointer(ref value); } - #region Generated Convert ByRef Delegates - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ConvertByrefToPtrDelegates from: generate_comdispatch.py - - private static readonly ConvertByrefToPtrDelegate s_convertSByteByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(sbyte))); - private static readonly ConvertByrefToPtrDelegate s_convertInt16ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(Int16))); - private static readonly ConvertByrefToPtrDelegate s_convertInt32ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(Int32))); - private static readonly ConvertByrefToPtrDelegate s_convertInt64ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(Int64))); - private static readonly ConvertByrefToPtrDelegate s_convertByteByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(byte))); - private static readonly ConvertByrefToPtrDelegate s_convertUInt16ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(UInt16))); - private static readonly ConvertByrefToPtrDelegate s_convertUInt32ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(UInt32))); - private static readonly ConvertByrefToPtrDelegate s_convertUInt64ByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(UInt64))); - private static readonly ConvertByrefToPtrDelegate s_convertIntPtrByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(IntPtr))); - private static readonly ConvertByrefToPtrDelegate s_convertUIntPtrByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(UIntPtr))); - private static readonly ConvertByrefToPtrDelegate s_convertSingleByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(Single))); - private static readonly ConvertByrefToPtrDelegate s_convertDoubleByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(double))); - private static readonly ConvertByrefToPtrDelegate s_convertDecimalByrefToPtr = (ConvertByrefToPtrDelegate)Delegate.CreateDelegate(typeof(ConvertByrefToPtrDelegate), s_convertByrefToPtr.MakeGenericMethod(typeof(decimal))); - - // *** END GENERATED CODE *** - - #endregion - - #region Generated Outer ConvertByrefToPtr - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ConvertByrefToPtr from: generate_comdispatch.py - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertSByteByrefToPtr(ref sbyte value) { return s_convertSByteByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertInt16ByrefToPtr(ref Int16 value) { return s_convertInt16ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertInt32ByrefToPtr(ref Int32 value) { return s_convertInt32ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertInt64ByrefToPtr(ref Int64 value) { return s_convertInt64ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertByteByrefToPtr(ref byte value) { return s_convertByteByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertUInt16ByrefToPtr(ref UInt16 value) { return s_convertUInt16ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertUInt32ByrefToPtr(ref UInt32 value) { return s_convertUInt32ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertUInt64ByrefToPtr(ref UInt64 value) { return s_convertUInt64ByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertIntPtrByrefToPtr(ref IntPtr value) { return s_convertIntPtrByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertUIntPtrByrefToPtr(ref UIntPtr value) { return s_convertUIntPtrByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertSingleByrefToPtr(ref Single value) { return s_convertSingleByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertDoubleByrefToPtr(ref double value) { return s_convertDoubleByrefToPtr(ref value); } - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertDecimalByrefToPtr(ref Decimal value) { return s_convertDecimalByrefToPtr(ref value); } - - // *** END GENERATED CODE *** - - #endregion - - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - public static IntPtr ConvertVariantByrefToPtr(ref Variant value) { return s_convertVariantByrefToPtr(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) { - Variant variant = default(Variant); + Variant variant = default; if (obj == null) { return variant; } - InitVariantForObject(obj, ref variant); return variant; } @@ -468,32 +322,31 @@ 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. - IDispatch disp = obj as IDispatch; - if (disp != null) + if (obj is IDispatch) { variant.AsDispatch = obj; return; } - System.Runtime.InteropServices.Marshal.GetNativeVariantForObject(obj, ConvertVariantByrefToPtr(ref variant)); + Marshal.GetNativeVariantForObject(obj, ConvertVariantByrefToPtr(ref variant)); } - [Obsolete("do not use this method", true)] + // This method is intended for use through reflection and should not be used directly public static object GetObjectForVariant(Variant variant) { IntPtr ptr = UnsafeMethods.ConvertVariantByrefToPtr(ref variant); - return System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(ptr); + return Marshal.GetObjectForNativeVariant(ptr); } - [Obsolete("do not use this method", true)] - public static int IUnknownRelease(IntPtr interfacePointer) + // This method is intended for use through reflection and should only be used directly by IUnknownReleaseNotZero + public static unsafe int IUnknownRelease(IntPtr interfacePointer) { - return s_IUnknownRelease(interfacePointer); + return ((delegate* unmanaged)(*(*(void***)interfacePointer + 2 /* IUnknown.Release slot */)))(interfacePointer); } - [Obsolete("do not use this method", true)] + // This method is intended for use through reflection and should not be used directly public static void IUnknownReleaseNotZero(IntPtr interfacePointer) { if (interfacePointer != IntPtr.Zero) @@ -502,47 +355,42 @@ public static void IUnknownReleaseNotZero(IntPtr interfacePointer) } } - [SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - [Obsolete("do not use this method", true)] - public static int IDispatchInvoke( + // This method is intended for use through reflection and should not be used directly + public static unsafe int IDispatchInvoke( IntPtr dispatchPointer, int memberDispId, ComTypes.INVOKEKIND flags, ref ComTypes.DISPPARAMS dispParams, out Variant result, out ExcepInfo excepInfo, - out uint argErr - ) + out uint argErr) { - int hresult = s_IDispatchInvoke( - dispatchPointer, - memberDispId, - flags, - ref dispParams, - out result, - out excepInfo, - out argErr - ); - - if (hresult == ComHresults.DISP_E_MEMBERNOTFOUND - && (flags & ComTypes.INVOKEKIND.INVOKE_FUNC) != 0 - && (flags & (ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT | ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF)) == 0) + Guid IID_NULL = default; + + fixed (ComTypes.DISPPARAMS* pDispParams = &dispParams) + fixed (Variant* pResult = &result) + fixed (ExcepInfo* pExcepInfo = &excepInfo) + fixed (uint* pArgErr = &argErr) { - // Re-invoke with no result argument to accomodate Word - hresult = _IDispatchInvokeNoResult( - dispatchPointer, - memberDispId, - ComTypes.INVOKEKIND.INVOKE_FUNC, - ref dispParams, - out result, - out excepInfo, - out argErr); - } + var pfnIDispatchInvoke = (delegate* unmanaged)(*(*(void***)dispatchPointer + 6 /* IDispatch.Invoke slot */)); + + int hresult = pfnIDispatchInvoke(dispatchPointer, + memberDispId, &IID_NULL, 0, (ushort)flags, pDispParams, pResult, pExcepInfo, pArgErr); - return hresult; + if (hresult == ComHresults.DISP_E_MEMBERNOTFOUND + && (flags & ComTypes.INVOKEKIND.INVOKE_FUNC) != 0 + && (flags & (ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT | ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF)) == 0) + { + // 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); + } + + return hresult; + } } - [Obsolete("do not use this method", true)] + // This method is intended for use through reflection and should not be used directly public static IntPtr GetIdsOfNamedParameters(IDispatch dispatch, string[] names, int methodDispId, out GCHandle pinningHandle) { pinningHandle = GCHandle.Alloc(null, GCHandleType.Pinned); @@ -569,61 +417,6 @@ public static IntPtr GetIdsOfNamedParameters(IDispatch dispatch, string[] names, #region non-public members - [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] - static UnsafeMethods() - { - } - - private static void EmitLoadArg(ILGenerator il, int index) - { - switch (index) - { - case 0: - il.Emit(OpCodes.Ldarg_0); - break; - case 1: - il.Emit(OpCodes.Ldarg_1); - break; - case 2: - il.Emit(OpCodes.Ldarg_2); - break; - case 3: - il.Emit(OpCodes.Ldarg_3); - break; - default: - if (index <= Byte.MaxValue) - { - il.Emit(OpCodes.Ldarg_S, (byte)index); - } - else - { - il.Emit(OpCodes.Ldarg, index); - } - - break; - } - } - - /// - /// Ensure that "value" is a local variable in some caller's frame. So converting - /// the byref to an IntPtr is a safe operation. Alternatively, we could also allow - /// allowed "value" to be a pinned object. - /// - [Conditional("DEBUG")] - public static void AssertByrefPointsToStack(IntPtr ptr) - { - if (Marshal.ReadInt32(ptr) == _dummyMarker) - { - // Prevent recursion - return; - } - - int dummy = _dummyMarker; - IntPtr ptrToLocal = ConvertInt32ByrefToPtr(ref dummy); - Debug.Assert(ptrToLocal.ToInt64() < ptr.ToInt64()); - Debug.Assert((ptr.ToInt64() - ptrToLocal.ToInt64()) < (16 * 1024)); - } - private static readonly object s_lock = new object(); private static ModuleBuilder s_dynamicModule; @@ -635,221 +428,19 @@ internal static ModuleBuilder DynamicModule { return s_dynamicModule; } - lock (s_lock) { if (s_dynamicModule == null) { - var attributes = new[] { - new CustomAttributeBuilder(typeof(UnverifiableCodeAttribute).GetConstructor(Type.EmptyTypes), Array.Empty()), - // PermissionSet(SecurityAction.Demand, Unrestricted = true) - new CustomAttributeBuilder(typeof(PermissionSetAttribute).GetConstructor(new Type[] { typeof(SecurityAction) }), - new object[] { SecurityAction.Demand }, - new PropertyInfo[] { typeof(PermissionSetAttribute).GetProperty("Unrestricted") }, - new object[] {true}) - }; - string name = typeof(VariantArray).Namespace + ".DynamicAssembly"; - var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(name), AssemblyBuilderAccess.Run, attributes); - assembly.DefineVersionInfoResource(); + var assembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), AssemblyBuilderAccess.Run); s_dynamicModule = assembly.DefineDynamicModule(name); } - return s_dynamicModule; } } } - private const int _dummyMarker = 0x10101010; - - /// - /// We will emit an indirect call to an unmanaged function pointer from the vtable of the given interface pointer. - /// This approach can take only ~300 instructions on x86 compared with ~900 for Marshal.Release. We are relying on - /// the JIT-compiler to do pinvoke-stub-inlining and calling the pinvoke target directly. - /// - private delegate int IUnknownReleaseDelegate(IntPtr interfacePointer); - private static readonly IUnknownReleaseDelegate s_IUnknownRelease = Create_IUnknownRelease(); - - private static IUnknownReleaseDelegate Create_IUnknownRelease() - { - DynamicMethod dm = new DynamicMethod("IUnknownRelease", typeof(int), new Type[] { typeof(IntPtr) }, DynamicModule); - - ILGenerator method = dm.GetILGenerator(); - - // return functionPtr(...) - - method.Emit(OpCodes.Ldarg_0); - - // functionPtr = *(IntPtr*)(*(interfacePointer) + VTABLE_OFFSET) - int iunknownReleaseOffset = ((int)IDispatchMethodIndices.IUnknown_Release) * Marshal.SizeOf(typeof(IntPtr)); - method.Emit(OpCodes.Ldarg_0); - method.Emit(OpCodes.Ldind_I); - method.Emit(OpCodes.Ldc_I4, iunknownReleaseOffset); - method.Emit(OpCodes.Add); - method.Emit(OpCodes.Ldind_I); - - System.Reflection.Emit.SignatureHelper signature = System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(CallingConvention.Winapi, typeof(int)); - signature.AddArgument(typeof(IntPtr)); - method.Emit(OpCodes.Calli, signature); - - method.Emit(OpCodes.Ret); - - return (IUnknownReleaseDelegate)dm.CreateDelegate(typeof(IUnknownReleaseDelegate)); - } - - internal static readonly IntPtr NullInterfaceId = GetNullInterfaceId(); - - private static IntPtr GetNullInterfaceId() - { - int size = Marshal.SizeOf(Guid.Empty); - IntPtr ptr = Marshal.AllocHGlobal(size); - for (int i = 0; i < size; i++) - { - Marshal.WriteByte(ptr, i, 0); - } - - return ptr; - } - - /// - /// We will emit an indirect call to an unmanaged function pointer from the vtable of the given IDispatch interface pointer. - /// It is not possible to express this in C#. Using an indirect pinvoke call allows us to do our own marshalling. - /// We can allocate the Variant arguments cheaply on the stack. We are relying on the JIT-compiler to do - /// pinvoke-stub-inlining and calling the pinvoke target directly. - /// The alternative of calling via a managed interface declaration of IDispatch would have a performance - /// penalty of going through a CLR stub that would have to re-push the arguments on the stack, etc. - /// Marshal.GetDelegateForFunctionPointer could be used here, but its too expensive (~2000 instructions on x86). - /// - private delegate int IDispatchInvokeDelegate( - IntPtr dispatchPointer, - int memberDispId, - ComTypes.INVOKEKIND flags, - ref ComTypes.DISPPARAMS dispParams, - out Variant result, - out ExcepInfo excepInfo, - out uint argErr - ); - - private static readonly IDispatchInvokeDelegate s_IDispatchInvoke = Create_IDispatchInvoke(true); - private static IDispatchInvokeDelegate s_IDispatchInvokeNoResultImpl; - - private static IDispatchInvokeDelegate _IDispatchInvokeNoResult - { - get - { - if (s_IDispatchInvokeNoResultImpl == null) - { - lock (s_IDispatchInvoke) - { - if (s_IDispatchInvokeNoResultImpl == null) - { - s_IDispatchInvokeNoResultImpl = Create_IDispatchInvoke(false); - } - } - } - - return s_IDispatchInvokeNoResultImpl; - } - } - - private static IDispatchInvokeDelegate Create_IDispatchInvoke(bool returnResult) - { - const int dispatchPointerIndex = 0; - const int memberDispIdIndex = 1; - const int flagsIndex = 2; - const int dispParamsIndex = 3; - const int resultIndex = 4; - const int exceptInfoIndex = 5; - const int argErrIndex = 6; - Debug.Assert(argErrIndex + 1 == typeof(IDispatchInvokeDelegate).GetMethod("Invoke").GetParameters().Length); - - Type[] paramTypes = new Type[argErrIndex + 1]; - paramTypes[dispatchPointerIndex] = typeof(IntPtr); - paramTypes[memberDispIdIndex] = typeof(int); - paramTypes[flagsIndex] = typeof(ComTypes.INVOKEKIND); - paramTypes[dispParamsIndex] = typeof(ComTypes.DISPPARAMS).MakeByRefType(); - paramTypes[resultIndex] = typeof(Variant).MakeByRefType(); - paramTypes[exceptInfoIndex] = typeof(ExcepInfo).MakeByRefType(); - paramTypes[argErrIndex] = typeof(uint).MakeByRefType(); - - // Define the dynamic method in our assembly so we skip verification - DynamicMethod dm = new DynamicMethod("IDispatchInvoke", typeof(int), paramTypes, DynamicModule); - ILGenerator method = dm.GetILGenerator(); - - // return functionPtr(...) - - EmitLoadArg(method, dispatchPointerIndex); - EmitLoadArg(method, memberDispIdIndex); - - // burn the address of our empty IID in directly. This is never freed, relocated, etc... - // Note passing this as a Guid directly results in a ~30% perf hit for IDispatch invokes so - // we also pass it directly as an IntPtr instead. - if (IntPtr.Size == 4) - { - method.Emit(OpCodes.Ldc_I4, UnsafeMethods.NullInterfaceId.ToInt32()); // riid - } - else - { - method.Emit(OpCodes.Ldc_I8, UnsafeMethods.NullInterfaceId.ToInt64()); // riid - } - - method.Emit(OpCodes.Conv_I); - - method.Emit(OpCodes.Ldc_I4_0); // lcid - EmitLoadArg(method, flagsIndex); - - EmitLoadArg(method, dispParamsIndex); - - if (returnResult) - { - EmitLoadArg(method, resultIndex); - } - else - { - method.Emit(OpCodes.Ldsfld, typeof(IntPtr).GetField("Zero")); - } - - EmitLoadArg(method, exceptInfoIndex); - EmitLoadArg(method, argErrIndex); - - // functionPtr = *(IntPtr*)(*(dispatchPointer) + VTABLE_OFFSET) - int idispatchInvokeOffset = ((int)IDispatchMethodIndices.IDispatch_Invoke) * Marshal.SizeOf(typeof(IntPtr)); - EmitLoadArg(method, dispatchPointerIndex); - method.Emit(OpCodes.Ldind_I); - method.Emit(OpCodes.Ldc_I4, idispatchInvokeOffset); - method.Emit(OpCodes.Add); - method.Emit(OpCodes.Ldind_I); - - System.Reflection.Emit.SignatureHelper signature = System.Reflection.Emit.SignatureHelper.GetMethodSigHelper(CallingConvention.Winapi, typeof(int)); - Type[] invokeParamTypes = new Type[] { - typeof(IntPtr), // dispatchPointer - typeof(int), // memberDispId - typeof(IntPtr), // riid - typeof(int), // lcid - typeof(ushort), // flags - typeof(IntPtr), // dispParams - typeof(IntPtr), // result - typeof(IntPtr), // excepInfo - typeof(IntPtr), // argErr - }; - signature.AddArguments(invokeParamTypes, null, null); - method.Emit(OpCodes.Calli, signature); - - method.Emit(OpCodes.Ret); - return (IDispatchInvokeDelegate)dm.CreateDelegate(typeof(IDispatchInvokeDelegate)); - } - #endregion } - - internal static class NativeMethods - { - [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.None)] - [System.Runtime.Versioning.ResourceConsumption(System.Runtime.Versioning.ResourceScope.Process, System.Runtime.Versioning.ResourceScope.Process)] - [DllImport("oleaut32.dll", PreserveSig = false)] - internal static extern void VariantClear(IntPtr variant); - } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComType.cs b/src/System.Management.Automation/engine/ComInterop/ComType.cs deleted file mode 100644 index fe76e682495..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComType.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -namespace System.Management.Automation.ComInterop -{ - internal enum ComType - { - Class, - Enum, - Interface - }; -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs index fcd9fc51bcc..2f2886d6555 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeClassDesc.cs @@ -1,14 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections.Generic; using System.Dynamic; +using System.Linq.Expressions; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop @@ -21,29 +17,20 @@ internal class ComTypeClassDesc : ComTypeDesc, IDynamicMetaObjectProvider public object CreateInstance() { - if (_typeObj == null) - { - _typeObj = System.Type.GetTypeFromCLSID(Guid); - } - - return System.Activator.CreateInstance(System.Type.GetTypeFromCLSID(Guid)); + _typeObj ??= Type.GetTypeFromCLSID(Guid); + return Activator.CreateInstance(Type.GetTypeFromCLSID(Guid)); } - internal ComTypeClassDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : - base(typeInfo, ComType.Class, typeLibDesc) + internal ComTypeClassDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : base(typeInfo, typeLibDesc) { ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo); Guid = typeAttr.guid; for (int i = 0; i < typeAttr.cImplTypes; i++) { - int hRefType; - typeInfo.GetRefTypeOfImplType(i, out hRefType); - ComTypes.ITypeInfo currentTypeInfo; - typeInfo.GetRefTypeInfo(hRefType, out currentTypeInfo); - - ComTypes.IMPLTYPEFLAGS implTypeFlags; - typeInfo.GetImplTypeFlags(i, out implTypeFlags); + typeInfo.GetRefTypeOfImplType(i, out int hRefType); + typeInfo.GetRefTypeInfo(hRefType, out ComTypes.ITypeInfo currentTypeInfo); + typeInfo.GetImplTypeFlags(i, out ComTypes.IMPLTYPEFLAGS implTypeFlags); bool isSourceItf = (implTypeFlags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0; AddInterface(currentTypeInfo, isSourceItf); @@ -56,20 +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); } } @@ -78,8 +57,8 @@ internal bool Implements(string itfName, bool isSourceItf) { if (isSourceItf) return _sourceItfs.Contains(itfName); - else - return _itfs.Contains(itfName); + + return _itfs.Contains(itfName); } #region IDynamicMetaObjectProvider Members @@ -92,6 +71,3 @@ public DynamicMetaObject GetMetaObject(Expression parameter) #endregion } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs index 12375c978fb..a4b90913e9b 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeDesc.cs @@ -1,65 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices.ComTypes; using System.Threading; -using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop { - internal class ComTypeDesc : ComTypeLibMemberDesc + internal class ComTypeDesc { - private string _typeName; - private string _documentation; - // Hashtable is threadsafe for multiple readers single writer. - // Enumerating and writing is mutually exclusive so require locking. - private Hashtable _putRefs; + private readonly string _typeName; + private readonly string _documentation; private ComMethodDesc _getItem; private ComMethodDesc _setItem; - internal ComTypeDesc(ITypeInfo typeInfo, ComType memberType, ComTypeLibDesc typeLibDesc) : base(memberType) + internal ComTypeDesc(ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) { if (typeInfo != null) { ComRuntimeHelpers.GetInfoFromType(typeInfo, out _typeName, out _documentation); } - TypeLib = typeLibDesc; } - internal static ComTypeDesc FromITypeInfo(ComTypes.ITypeInfo typeInfo, ComTypes.TYPEATTR typeAttr) + internal static ComTypeDesc FromITypeInfo(ITypeInfo typeInfo, TYPEATTR typeAttr) { - if (typeAttr.typekind == ComTypes.TYPEKIND.TKIND_COCLASS) - { - return new ComTypeClassDesc(typeInfo, null); - } - else if (typeAttr.typekind == ComTypes.TYPEKIND.TKIND_ENUM) + switch (typeAttr.typekind) { - return new ComTypeEnumDesc(typeInfo, null); - } - else if ((typeAttr.typekind == ComTypes.TYPEKIND.TKIND_DISPATCH) || - (typeAttr.typekind == ComTypes.TYPEKIND.TKIND_INTERFACE)) - { - ComTypeDesc typeDesc = new ComTypeDesc(typeInfo, ComType.Interface, null); - return typeDesc; - } - else - { - throw new InvalidOperationException("Attempting to wrap an unsupported enum type."); + case TYPEKIND.TKIND_COCLASS: + return new ComTypeClassDesc(typeInfo, null); + case TYPEKIND.TKIND_ENUM: + return new ComTypeEnumDesc(typeInfo, null); + case TYPEKIND.TKIND_DISPATCH: + case TYPEKIND.TKIND_INTERFACE: + ComTypeDesc typeDesc = new ComTypeDesc(typeInfo, null); + return typeDesc; + default: + throw new InvalidOperationException("Attempting to wrap an unsupported enum type."); } } internal static ComTypeDesc CreateEmptyTypeDesc() { - ComTypeDesc typeDesc = new ComTypeDesc(null, ComType.Interface, null); - typeDesc.Funcs = new Hashtable(); - typeDesc.Puts = new Hashtable(); - typeDesc._putRefs = new Hashtable(); - typeDesc.Events = EmptyEvents; + ComTypeDesc typeDesc = new ComTypeDesc(null, null) + { + Funcs = new Hashtable(), + Puts = new Hashtable(), + PutRefs = new Hashtable(), + Events = EmptyEvents + }; return typeDesc; } @@ -70,10 +61,7 @@ internal static ComTypeDesc CreateEmptyTypeDesc() internal Hashtable Puts { get; set; } - internal Hashtable PutRefs - { - set { _putRefs = value; } - } + internal Hashtable PutRefs { get; set; } internal Dictionary Events { get; set; } @@ -85,7 +73,6 @@ internal bool TryGetFunc(string name, out ComMethodDesc method) method = Funcs[name] as ComMethodDesc; return true; } - method = null; return false; } @@ -107,7 +94,6 @@ internal bool TryGetPut(string name, out ComMethodDesc method) method = Puts[name] as ComMethodDesc; return true; } - method = null; return false; } @@ -124,12 +110,11 @@ internal void AddPut(string name, ComMethodDesc method) internal bool TryGetPutRef(string name, out ComMethodDesc method) { name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture); - if (_putRefs.ContainsKey(name)) + if (PutRefs.ContainsKey(name)) { - method = _putRefs[name] as ComMethodDesc; + method = PutRefs[name] as ComMethodDesc; return true; } - method = null; return false; } @@ -137,9 +122,9 @@ internal bool TryGetPutRef(string name, out ComMethodDesc method) internal void AddPutRef(string name, ComMethodDesc method) { name = name.ToUpper(System.Globalization.CultureInfo.InvariantCulture); - lock (_putRefs) + lock (PutRefs) { - _putRefs[name] = method; + PutRefs[name] = method; } } @@ -177,9 +162,9 @@ internal string[] GetMemberNames(bool dataOnly) } } - lock (_putRefs) + lock (PutRefs) { - foreach (ComMethodDesc func in _putRefs.Values) + foreach (ComMethodDesc func in PutRefs.Values) { if (!names.ContainsKey(func.Name)) { @@ -192,47 +177,35 @@ internal string[] GetMemberNames(bool dataOnly) { foreach (string name in Events.Keys) { - names.TryAdd(name, null); + if (!names.ContainsKey(name)) + { + names.Add(name, null); + } } } } - var keys = names.Keys; - string[] result = new string[keys.Count]; - keys.CopyTo(result, 0); + string[] result = new string[names.Keys.Count]; + names.Keys.CopyTo(result, 0); return result; } - // this property is public - accessed by an AST - public string TypeName - { - get { return _typeName; } - } + public string TypeName => _typeName; - internal string Documentation - { - get { return _documentation; } - } + internal string Documentation => _documentation; - // this property is public - accessed by an AST public ComTypeLibDesc TypeLib { get; } internal Guid Guid { get; set; } - internal ComMethodDesc GetItem - { - get { return _getItem; } - } + internal ComMethodDesc GetItem => _getItem; internal void EnsureGetItem(ComMethodDesc candidate) { Interlocked.CompareExchange(ref _getItem, candidate, null); } - internal ComMethodDesc SetItem - { - get { return _setItem; } - } + internal ComMethodDesc SetItem => _setItem; internal void EnsureSetItem(ComMethodDesc candidate) { @@ -240,6 +213,3 @@ internal void EnsureSetItem(ComMethodDesc candidate) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs index c873a1f8d5e..00fe57c2b44 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeEnumDesc.cs @@ -1,16 +1,11 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Runtime.InteropServices; +using System; using System.Dynamic; using System.Globalization; +using System.Linq.Expressions; +using System.Runtime.InteropServices; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop @@ -20,19 +15,15 @@ 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, ComType.Enum, typeLibDesc) + internal ComTypeEnumDesc(ComTypes.ITypeInfo typeInfo, ComTypeLibDesc typeLibDesc) : base(typeInfo, typeLibDesc) { ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo); string[] memberNames = new string[typeAttr.cVars]; object[] memberValues = new object[typeAttr.cVars]; - IntPtr p = IntPtr.Zero; + IntPtr p; // For each enum member get name and value. for (int i = 0; i < typeAttr.cVars; i++) @@ -93,13 +84,9 @@ internal bool HasMember(string name) return false; } - // TODO: internal public string[] GetMemberNames() { return (string[])_memberNames.Clone(); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs index 5bded65e70a..8f373e52a72 100644 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs +++ b/src/System.Management.Automation/engine/ComInterop/ComTypeLibDesc.cs @@ -1,16 +1,12 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections.Generic; -using System.Runtime.InteropServices; using System.Dynamic; using System.Globalization; +using System.Linq.Expressions; +using System.Runtime.InteropServices; using ComTypes = System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop @@ -24,11 +20,11 @@ internal sealed class ComTypeLibDesc : IDynamicMetaObjectProvider // typically typelibs contain very small number of coclasses // so we will just use the linked list as it performs better // on small number of entities - private LinkedList _classes; - private Dictionary _enums; + private readonly LinkedList _classes; + private readonly Dictionary _enums; private ComTypes.TYPELIBATTR _typeLibAttributes; - private static Dictionary s_cachedTypeLibDesc = new Dictionary(); + private static readonly Dictionary s_cachedTypeLibDesc = new Dictionary(); private ComTypeLibDesc() { @@ -36,12 +32,8 @@ private ComTypeLibDesc() _classes = new LinkedList(); } - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "", Name); - } + public override string ToString() => $""; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] public string Documentation { get { return string.Empty; } @@ -56,49 +48,6 @@ DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) #endregion - /// - /// Reads the latest registered type library for the corresponding GUID, - /// reads definitions of CoClasses and Enum's from this library - /// and creates a IDynamicMetaObjectProvider that allows to instantiate coclasses - /// and get actual values for the enums. - /// - /// Type Library Guid. - /// ComTypeLibDesc object. - [System.Runtime.Versioning.ResourceExposure(System.Runtime.Versioning.ResourceScope.Machine)] - [System.Runtime.Versioning.ResourceConsumption(System.Runtime.Versioning.ResourceScope.Machine, System.Runtime.Versioning.ResourceScope.Machine)] - public static ComTypeLibInfo CreateFromGuid(Guid typeLibGuid) - { - // passing majorVersion = -1, minorVersion = -1 will always - // load the latest typelib - ComTypes.ITypeLib typeLib = UnsafeMethods.LoadRegTypeLib(ref typeLibGuid, -1, -1, 0); - - return new ComTypeLibInfo(GetFromTypeLib(typeLib)); - } - - /// - /// Gets an ITypeLib object from OLE Automation compatible RCW , - /// reads definitions of CoClasses and Enum's from this library - /// and creates a IDynamicMetaObjectProvider that allows to instantiate coclasses - /// and get actual values for the enums. - /// - /// OLE automation compatible RCW. - /// ComTypeLibDesc object. - public static ComTypeLibInfo CreateFromObject(object rcw) - { - if (Marshal.IsComObject(rcw) == false) - { - throw new ArgumentException("COM object is expected."); - } - - ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(rcw as IDispatch, true); - - ComTypes.ITypeLib typeLib; - int typeInfoIndex; - typeInfo.GetContainingTypeLib(out typeLib, out typeInfoIndex); - - return new ComTypeLibInfo(GetFromTypeLib(typeLib)); - } - internal static ComTypeLibDesc GetFromTypeLib(ComTypes.ITypeLib typeLib) { // check whether we have already loaded this type library @@ -112,19 +61,18 @@ internal static ComTypeLibDesc GetFromTypeLib(ComTypes.ITypeLib typeLib) } } - typeLibDesc = new ComTypeLibDesc(); - - typeLibDesc.Name = ComRuntimeHelpers.GetNameOfLib(typeLib); - typeLibDesc._typeLibAttributes = typeLibAttr; + typeLibDesc = new ComTypeLibDesc + { + Name = ComRuntimeHelpers.GetNameOfLib(typeLib), + _typeLibAttributes = typeLibAttr + }; int countTypes = typeLib.GetTypeInfoCount(); for (int i = 0; i < countTypes; i++) { - ComTypes.TYPEKIND typeKind; - typeLib.GetTypeInfoType(i, out typeKind); + typeLib.GetTypeInfoType(i, out ComTypes.TYPEKIND typeKind); - ComTypes.ITypeInfo typeInfo; - typeLib.GetTypeInfo(i, out typeInfo); + typeLib.GetTypeInfo(i, out ComTypes.ITypeInfo typeInfo); if (typeKind == ComTypes.TYPEKIND.TKIND_COCLASS) { ComTypeClassDesc classDesc = new ComTypeClassDesc(typeInfo, typeLibDesc); @@ -140,11 +88,9 @@ internal static ComTypeLibDesc GetFromTypeLib(ComTypes.ITypeLib typeLib) ComTypes.TYPEATTR typeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(typeInfo); if (typeAttr.tdescAlias.vt == (short)VarEnum.VT_USERDEFINED) { - string aliasName, documentation; - ComRuntimeHelpers.GetInfoFromType(typeInfo, out aliasName, out documentation); + ComRuntimeHelpers.GetInfoFromType(typeInfo, out string aliasName, out _); - ComTypes.ITypeInfo referencedTypeInfo; - typeInfo.GetRefTypeInfo(typeAttr.tdescAlias.lpValue.ToInt32(), out referencedTypeInfo); + typeInfo.GetRefTypeInfo(typeAttr.tdescAlias.lpValue.ToInt32(), out ComTypes.ITypeInfo referencedTypeInfo); ComTypes.TYPEATTR referencedTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(referencedTypeInfo); ComTypes.TYPEKIND referencedTypeKind = referencedTypeAttr.typekind; @@ -177,14 +123,12 @@ public object GetTypeLibObjectDesc(string member) } } - ComTypeEnumDesc enumDesc; - if (_enums != null && _enums.TryGetValue(member, out enumDesc) == true) + if (_enums != null && _enums.TryGetValue(member, out ComTypeEnumDesc enumDesc)) return enumDesc; return null; } - // TODO: internal public string[] GetMemberNames() { string[] retval = new string[_enums.Count + _classes.Count]; @@ -212,26 +156,13 @@ internal bool HasMember(string member) } } - if (_enums.ContainsKey(member) == true) + if (_enums.ContainsKey(member)) return true; return false; } - public Guid Guid - { - get { return _typeLibAttributes.guid; } - } - - public short VersionMajor - { - get { return _typeLibAttributes.wMajorVerNum; } - } - - public short VersionMinor - { - get { return _typeLibAttributes.wMinorVerNum; } - } + public Guid Guid => _typeLibAttributes.guid; public string Name { get; private set; } @@ -249,6 +180,3 @@ internal ComTypeClassDesc GetCoClassForInterface(string itfName) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeLibInfo.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeLibInfo.cs deleted file mode 100644 index 5863424275d..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeLibInfo.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Dynamic; - -namespace System.Management.Automation.ComInterop -{ - internal sealed class ComTypeLibInfo : IDynamicMetaObjectProvider - { - internal ComTypeLibInfo(ComTypeLibDesc typeLibDesc) - { - TypeLibDesc = typeLibDesc; - } - - public string Name - { - get { return TypeLibDesc.Name; } - } - - public Guid Guid - { - get { return TypeLibDesc.Guid; } - } - - public short VersionMajor - { - get { return TypeLibDesc.VersionMajor; } - } - - public short VersionMinor - { - get { return TypeLibDesc.VersionMinor; } - } - - public ComTypeLibDesc TypeLibDesc { get; } - - // TODO: internal - public string[] GetMemberNames() - { - return new string[] { this.Name, "Guid", "Name", "VersionMajor", "VersionMinor" }; - } - - DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) - { - return new TypeLibInfoMetaObject(parameter, this); - } - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ComTypeLibMemberDesc.cs b/src/System.Management.Automation/engine/ComInterop/ComTypeLibMemberDesc.cs deleted file mode 100644 index 494425213b0..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/ComTypeLibMemberDesc.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -namespace System.Management.Automation.ComInterop -{ - internal class ComTypeLibMemberDesc - { - internal ComTypeLibMemberDesc(ComType kind) - { - Kind = kind; - } - - public ComType Kind { get; } - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ConversionArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/ConversionArgBuilder.cs index caff255c02a..c8aeaaf4ba4 100644 --- a/src/System.Management.Automation/engine/ComInterop/ConversionArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ConversionArgBuilder.cs @@ -1,21 +1,16 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -//using Microsoft.Scripting.Utils; using System.Management.Automation.Interpreter; namespace System.Management.Automation.ComInterop { internal class ConversionArgBuilder : ArgBuilder { - private SimpleArgBuilder _innerBuilder; - private Type _parameterType; + private readonly SimpleArgBuilder _innerBuilder; + private readonly Type _parameterType; internal ConversionArgBuilder(Type parameterType, SimpleArgBuilder innerBuilder) { @@ -30,11 +25,8 @@ internal override Expression Marshal(Expression parameter) internal override Expression MarshalToRef(Expression parameter) { - // we are not supporting conversion InOut + //we are not supporting conversion InOut throw Assert.Unreachable; } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ConvertArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/ConvertArgBuilder.cs index e177898e6d1..9200a6679e7 100644 --- a/src/System.Management.Automation/engine/ComInterop/ConvertArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ConvertArgBuilder.cs @@ -1,13 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT - -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif namespace System.Management.Automation.ComInterop { @@ -33,6 +28,3 @@ internal override Expression UnmarshalFromRef(Expression newValue) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ConvertibleArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/ConvertibleArgBuilder.cs index 4c446b7ac95..249b4d00b8a 100644 --- a/src/System.Management.Automation/engine/ComInterop/ConvertibleArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ConvertibleArgBuilder.cs @@ -1,24 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT - -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -//using Microsoft.Scripting.Utils; -using System.Management.Automation.Interpreter; namespace System.Management.Automation.ComInterop { internal class ConvertibleArgBuilder : ArgBuilder { - internal ConvertibleArgBuilder() - { - } - internal override Expression Marshal(Expression parameter) { return Helpers.Convert(parameter, typeof(IConvertible)); @@ -26,11 +15,8 @@ internal override Expression Marshal(Expression parameter) internal override Expression MarshalToRef(Expression parameter) { - // we are not supporting convertible InOut - throw Assert.Unreachable; + //we are not supporting convertible InOut + throw new NotSupportedException(); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/CurrencyArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/CurrencyArgBuilder.cs index 3ad1264b2d0..672a674c0e6 100644 --- a/src/System.Management.Automation/engine/ComInterop/CurrencyArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/CurrencyArgBuilder.cs @@ -1,13 +1,11 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +#pragma warning disable 618 // CurrencyWrapper is obsolete + +using System; using System.Diagnostics; +using System.Linq.Expressions; using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -25,7 +23,7 @@ internal override Expression Marshal(Expression parameter) // parameter.WrappedObject return Expression.Property( Helpers.Convert(base.Marshal(parameter), typeof(CurrencyWrapper)), - "WrappedObject" + nameof(CurrencyWrapper.WrappedObject) ); } @@ -33,7 +31,7 @@ internal override Expression MarshalToRef(Expression parameter) { // Decimal.ToOACurrency(parameter.WrappedObject) return Expression.Call( - typeof(decimal).GetMethod("ToOACurrency"), + typeof(decimal).GetMethod(nameof(decimal.ToOACurrency)), Marshal(parameter) ); } @@ -45,7 +43,7 @@ internal override Expression UnmarshalFromRef(Expression value) Expression.New( typeof(CurrencyWrapper).GetConstructor(new Type[] { typeof(decimal) }), Expression.Call( - typeof(decimal).GetMethod("FromOACurrency"), + typeof(decimal).GetMethod(nameof(decimal.FromOACurrency)), value ) ) @@ -53,6 +51,3 @@ internal override Expression UnmarshalFromRef(Expression value) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/DateTimeArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/DateTimeArgBuilder.cs index a69037ab3f7..3f56a0004ac 100644 --- a/src/System.Management.Automation/engine/ComInterop/DateTimeArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/DateTimeArgBuilder.cs @@ -1,13 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Diagnostics; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -24,7 +20,7 @@ internal override Expression MarshalToRef(Expression parameter) // parameter.ToOADate() return Expression.Call( Marshal(parameter), - typeof(DateTime).GetMethod("ToOADate") + typeof(DateTime).GetMethod(nameof(DateTime.ToOADate)) ); } @@ -33,13 +29,10 @@ internal override Expression UnmarshalFromRef(Expression value) // DateTime.FromOADate(value) return base.UnmarshalFromRef( Expression.Call( - typeof(DateTime).GetMethod("FromOADate"), + typeof(DateTime).GetMethod(nameof(DateTime.FromOADate)), value ) ); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/DispCallable.cs b/src/System.Management.Automation/engine/ComInterop/DispCallable.cs index aba2fc5f6f2..e99bdd3741f 100644 --- a/src/System.Management.Automation/engine/ComInterop/DispCallable.cs +++ b/src/System.Management.Automation/engine/ComInterop/DispCallable.cs @@ -1,14 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Dynamic; using System.Globalization; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -24,17 +19,11 @@ 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; } - public IDispatch DispatchObject - { - get { return DispatchComObject.DispatchObject; } - } + public IDispatch DispatchObject => DispatchComObject.DispatchObject; public string MemberName { get; } @@ -47,8 +36,7 @@ public DynamicMetaObject GetMetaObject(Expression parameter) public override bool Equals(object obj) { - var other = obj as DispCallable; - return other != null && other.DispatchComObject == DispatchComObject && other.DispId == DispId; + return obj is DispCallable other && other.DispatchComObject == DispatchComObject && other.DispId == DispId; } public override int GetHashCode() @@ -57,6 +45,3 @@ public override int GetHashCode() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/DispCallableMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/DispCallableMetaObject.cs index 130eaa524c2..f7377804694 100644 --- a/src/System.Management.Automation/engine/ComInterop/DispCallableMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/DispCallableMetaObject.cs @@ -1,16 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Linq; -using System.Dynamic; using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; using System.Management.Automation.Language; namespace System.Management.Automation.ComInterop @@ -39,11 +33,10 @@ public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObj private DynamicMetaObject BindGetOrInvoke(DynamicMetaObject[] args, CallInfo callInfo) { - ComMethodDesc method; - var target = _callable.DispatchComObject; - var name = _callable.MemberName; + IDispatchComObject target = _callable.DispatchComObject; + string name = _callable.MemberName; - if (target.TryGetMemberMethod(name, out method) || + if (target.TryGetMemberMethod(name, out ComMethodDesc method) || target.TryGetMemberMethodExplicit(name, out method)) { List temps = new List(); @@ -52,18 +45,16 @@ private DynamicMetaObject BindGetOrInvoke(DynamicMetaObject[] args, CallInfo cal bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(method, ref args, temps, initTemps); return BindComInvoke(method, args, callInfo, isByRef, temps, initTemps); } - return null; } public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - ComMethodDesc method; - var target = _callable.DispatchComObject; - var name = _callable.MemberName; + IDispatchComObject target = _callable.DispatchComObject; + string name = _callable.MemberName; bool holdsNull = value.Value == null && value.HasValue; - if (target.TryGetPropertySetter(name, out method, value.LimitType, holdsNull) || + if (target.TryGetPropertySetter(name, out ComMethodDesc method, value.LimitType, holdsNull) || target.TryGetPropertySetterExplicit(name, out method, value.LimitType, holdsNull)) { List temps = new List(); @@ -71,14 +62,16 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet bool[] isByRef = ComBinderHelpers.ProcessArgumentsForCom(method, ref indexes, temps, initTemps); isByRef = isByRef.AddLast(false); - // Convert the value to the target type - DynamicMetaObject updatedValue = new DynamicMetaObject(value.CastOrConvertMethodArgument( - value.LimitType, - name, - "SetIndex", - temps, - initTemps), value.Restrictions); + DynamicMetaObject updatedValue = new DynamicMetaObject( + value.CastOrConvertMethodArgument( + value.LimitType, + name, + "SetIndex", + allowCastingToByRefLikeType: false, + temps, + initTemps), + value.Restrictions); var result = BindComInvoke(method, indexes.AddLast(updatedValue), binder.CallInfo, isByRef, temps, initTemps); @@ -95,8 +88,8 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet private DynamicMetaObject BindComInvoke(ComMethodDesc method, DynamicMetaObject[] indexes, CallInfo callInfo, bool[] isByRef, List temps, List initTemps) { - var callable = Expression; - var dispCall = Helpers.Convert(callable, typeof(DispCallable)); + Expression callable = Expression; + Expression dispCall = Helpers.Convert(callable, typeof(DispCallable)); DynamicMetaObject invoke = new ComInvokeBinder( callInfo, @@ -106,12 +99,12 @@ private DynamicMetaObject BindComInvoke(ComMethodDesc method, DynamicMetaObject[ Expression.Constant(method), Expression.Property( dispCall, - typeof(DispCallable).GetProperty("DispatchObject") + typeof(DispCallable).GetProperty(nameof(DispCallable.DispatchObject)) ), method ).Invoke(); - if ((temps != null) && (temps.Any())) + if (temps != null && temps.Count > 0) { Expression invokeExpression = invoke.Expression; Expression call = Expression.Block(invokeExpression.Type, temps, initTemps.Append(invokeExpression)); @@ -123,15 +116,15 @@ private DynamicMetaObject BindComInvoke(ComMethodDesc method, DynamicMetaObject[ private BindingRestrictions DispCallableRestrictions() { - var callable = Expression; + Expression callable = Expression; - var callableTypeRestrictions = BindingRestrictions.GetTypeRestriction(callable, typeof(DispCallable)); - var dispCall = Helpers.Convert(callable, typeof(DispCallable)); - var dispatch = Expression.Property(dispCall, typeof(DispCallable).GetProperty("DispatchComObject")); - var dispId = Expression.Property(dispCall, typeof(DispCallable).GetProperty("DispId")); + BindingRestrictions callableTypeRestrictions = BindingRestrictions.GetTypeRestriction(callable, typeof(DispCallable)); + Expression dispCall = Helpers.Convert(callable, typeof(DispCallable)); + MemberExpression dispatch = Expression.Property(dispCall, typeof(DispCallable).GetProperty(nameof(DispCallable.DispatchComObject))); + MemberExpression dispId = Expression.Property(dispCall, typeof(DispCallable).GetProperty(nameof(DispCallable.DispId))); - var dispatchRestriction = IDispatchMetaObject.IDispatchRestriction(dispatch, _callable.DispatchComObject.ComTypeDesc); - var memberRestriction = BindingRestrictions.GetExpressionRestriction( + BindingRestrictions dispatchRestriction = IDispatchMetaObject.IDispatchRestriction(dispatch, _callable.DispatchComObject.ComTypeDesc); + BindingRestrictions memberRestriction = BindingRestrictions.GetExpressionRestriction( Expression.Equal(dispId, Expression.Constant(_callable.DispId)) ); @@ -139,6 +132,3 @@ private BindingRestrictions DispCallableRestrictions() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/DispatchArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/DispatchArgBuilder.cs index 1c0c0b23dc5..2060f986a22 100644 --- a/src/System.Management.Automation/engine/ComInterop/DispatchArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/DispatchArgBuilder.cs @@ -1,12 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -30,9 +26,9 @@ internal override Expression Marshal(Expression parameter) { parameter = Expression.Property( Helpers.Convert(parameter, typeof(DispatchWrapper)), - typeof(DispatchWrapper).GetProperty("WrappedObject") + typeof(DispatchWrapper).GetProperty(nameof(DispatchWrapper.WrappedObject)) ); - }; + } return Helpers.Convert(parameter, typeof(object)); } @@ -46,7 +42,7 @@ internal override Expression MarshalToRef(Expression parameter) Expression.Equal(parameter, Expression.Constant(null)), Expression.Constant(IntPtr.Zero), Expression.Call( - typeof(Marshal).GetMethod("GetIDispatchForObject"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.GetIDispatchForObject)), parameter ) ); @@ -59,7 +55,7 @@ internal override Expression UnmarshalFromRef(Expression value) Expression.Equal(value, Expression.Constant(IntPtr.Zero)), Expression.Constant(null), Expression.Call( - typeof(Marshal).GetMethod("GetObjectForIUnknown"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.GetObjectForIUnknown)), value ) ); @@ -76,6 +72,3 @@ internal override Expression UnmarshalFromRef(Expression value) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/ErrorArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/ErrorArgBuilder.cs index e0a95e37bce..568bb8559ef 100644 --- a/src/System.Management.Automation/engine/ComInterop/ErrorArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/ErrorArgBuilder.cs @@ -1,15 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject - -#if !CLR2 +using System; +using System.Diagnostics; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Runtime.InteropServices; -using System.Diagnostics; namespace System.Management.Automation.ComInterop { @@ -26,7 +21,7 @@ internal override Expression Marshal(Expression parameter) // parameter.ErrorCode return Expression.Property( Helpers.Convert(base.Marshal(parameter), typeof(ErrorWrapper)), - "ErrorCode" + nameof(ErrorWrapper.ErrorCode) ); } @@ -42,6 +37,3 @@ internal override Expression UnmarshalFromRef(Expression value) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/Errors.cs b/src/System.Management.Automation/engine/ComInterop/Errors.cs index 0756a1f5312..4939b511233 100644 --- a/src/System.Management.Automation/engine/ComInterop/Errors.cs +++ b/src/System.Management.Automation/engine/ComInterop/Errors.cs @@ -1,134 +1,16 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Management.Automation.Internal; namespace System.Management.Automation.ComInterop { - #region Generated Com Exception Factory - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_expr_factory_com from: generate_exception_factory.py - - /// - /// Strongly-typed and parameterized string factory. - /// - - internal static partial class Strings - { - private static string FormatString(string format, params object[] args) - { - return string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args); - } - - /// - /// A string like "Unexpected VarEnum {0}." - /// - internal static string UnexpectedVarEnum(object p0) - { - return FormatString(ParserStrings.UnexpectedVarEnum, p0); - } - - /// - /// A string like "Error while invoking {0}." - /// - internal static string DispBadParamCount(object p0, int parameterCount) - { - return FormatString(ParserStrings.DispBadParamCount, p0, parameterCount); - } - - /// - /// A string like "Error while invoking {0}." - /// - internal static string DispMemberNotFound(object p0) - { - return FormatString(ParserStrings.DispMemberNotFound, p0); - } - - /// - /// A string like "Error while invoking {0}. Named arguments are not supported." - /// - internal static string DispNoNamedArgs(object p0) - { - return FormatString(ParserStrings.DispNoNamedArgs, p0); - } - - /// - /// A string like "Error while invoking {0}." - /// - internal static string DispOverflow(object p0) - { - return FormatString(ParserStrings.DispOverflow, p0); - } - - /// - /// A string like "Exception setting "{0}": "Cannot convert the "{1}" value of type "{2}" to type "{3}"." - /// - internal static string DispTypeMismatch(object method, string value, string originalTypeName, string destinationTypeName) - { - return FormatString(ParserStrings.DispTypeMismatch, method, value, originalTypeName, destinationTypeName); - } - - /// - /// A string like "Error while invoking {0}. A required parameter was omitted." - /// - internal static string DispParamNotOptional(object p0) - { - return FormatString(ParserStrings.DispParamNotOptional, p0); - } - - /// - /// A string like "IDispatch::GetIDsOfNames behaved unexpectedly for {0}." - /// - internal static string GetIDsOfNamesInvalid(object p0) - { - return FormatString(ParserStrings.GetIDsOfNamesInvalid, p0); - } - - /// - /// A string like "Could not get dispatch ID for {0} (error: {1})." - /// - internal static string CouldNotGetDispId(object p0, object p1) - { - return FormatString(ParserStrings.CouldNotGetDispId, p0, p1); - } - - /// - /// A string like "There are valid conversions from {0} to {1}." - /// - internal static string AmbiguousConversion(object p0, object p1) - { - return FormatString(ParserStrings.AmbiguousConversion, p0, p1); - } - - /// - /// A string like "Variant.GetAccessor cannot handle {0}." - /// - internal static string VariantGetAccessorNYI(object p0) - { - return FormatString(ParserStrings.VariantGetAccessorNYI, p0); - } - } /// /// Strongly-typed and parameterized exception factory. /// - internal static partial class Error { - /// - /// ArgumentException with message like "COM object does not support events." - /// - internal static Exception COMObjectDoesNotSupportEvents() - { - return new ArgumentException(ParserStrings.COMObjectDoesNotSupportEvents); - } - - /// - /// ArgumentException with message like "COM object does not support specified source interface." - /// - internal static Exception COMObjectDoesNotSupportSourceInterface() - { - return new ArgumentException(ParserStrings.COMObjectDoesNotSupportSourceInterface); - } - /// /// InvalidOperationException with message like "Marshal.SetComObjectData failed." /// @@ -137,20 +19,12 @@ internal static Exception SetComObjectDataFailed() return new InvalidOperationException(ParserStrings.SetComObjectDataFailed); } - /// - /// InvalidOperationException with message like "This method exists only to keep the compiler happy." - /// - internal static Exception MethodShouldNotBeCalled() - { - return new InvalidOperationException(ParserStrings.MethodShouldNotBeCalled); - } - /// /// InvalidOperationException with message like "Unexpected VarEnum {0}." /// internal static Exception UnexpectedVarEnum(object p0) { - return new InvalidOperationException(Strings.UnexpectedVarEnum(p0)); + return new InvalidOperationException(StringUtil.Format(ParserStrings.UnexpectedVarEnum, p0)); } /// @@ -158,7 +32,7 @@ internal static Exception UnexpectedVarEnum(object p0) /// internal static Exception DispBadParamCount(object p0, int parameterCount) { - return new System.Reflection.TargetParameterCountException(Strings.DispBadParamCount(p0, parameterCount)); + return new System.Reflection.TargetParameterCountException(StringUtil.Format(ParserStrings.DispBadParamCount, p0, parameterCount)); } /// @@ -166,7 +40,7 @@ internal static Exception DispBadParamCount(object p0, int parameterCount) /// internal static Exception DispMemberNotFound(object p0) { - return new MissingMemberException(Strings.DispMemberNotFound(p0)); + return new MissingMemberException(StringUtil.Format(ParserStrings.DispMemberNotFound, p0)); } /// @@ -174,7 +48,7 @@ internal static Exception DispMemberNotFound(object p0) /// internal static Exception DispNoNamedArgs(object p0) { - return new ArgumentException(Strings.DispNoNamedArgs(p0)); + return new ArgumentException(StringUtil.Format(ParserStrings.DispNoNamedArgs, p0)); } /// @@ -182,7 +56,7 @@ internal static Exception DispNoNamedArgs(object p0) /// internal static Exception DispOverflow(object p0) { - return new OverflowException(Strings.DispOverflow(p0)); + return new OverflowException(StringUtil.Format(ParserStrings.DispOverflow, p0)); } /// @@ -190,7 +64,7 @@ internal static Exception DispOverflow(object p0) /// internal static Exception DispTypeMismatch(object method, string value, string originalTypeName, string destinationTypeName) { - return new ArgumentException(Strings.DispTypeMismatch(method, value, originalTypeName, destinationTypeName)); + return new ArgumentException(StringUtil.Format(ParserStrings.DispTypeMismatch, method, value, originalTypeName, destinationTypeName)); } /// @@ -198,11 +72,11 @@ internal static Exception DispTypeMismatch(object method, string value, string o /// internal static Exception DispParamNotOptional(object p0) { - return new ArgumentException(Strings.DispParamNotOptional(p0)); + return new ArgumentException(StringUtil.Format(ParserStrings.DispParamNotOptional, p0)); } /// - /// InvalidOperationException with message like "ResolveComReference.CannotRetrieveTypeInformation." + /// InvalidOperationException with message like "Cannot retrieve type information." /// internal static Exception CannotRetrieveTypeInformation() { @@ -214,15 +88,7 @@ internal static Exception CannotRetrieveTypeInformation() /// internal static Exception GetIDsOfNamesInvalid(object p0) { - return new ArgumentException(Strings.GetIDsOfNamesInvalid(p0)); - } - - /// - /// InvalidOperationException with message like "Attempting to wrap an unsupported enum type." - /// - internal static Exception UnsupportedEnumType() - { - return new InvalidOperationException(ParserStrings.UnsupportedEnumType); + return new ArgumentException(StringUtil.Format(ParserStrings.GetIDsOfNamesInvalid, p0)); } /// @@ -238,7 +104,7 @@ internal static Exception UnsupportedHandlerType() /// internal static Exception CouldNotGetDispId(object p0, object p1) { - return new MissingMemberException(Strings.CouldNotGetDispId(p0, p1)); + return new MissingMemberException(StringUtil.Format(ParserStrings.CouldNotGetDispId, p0, p1)); } /// @@ -246,20 +112,7 @@ internal static Exception CouldNotGetDispId(object p0, object p1) /// internal static Exception AmbiguousConversion(object p0, object p1) { - return new System.Reflection.AmbiguousMatchException(Strings.AmbiguousConversion(p0, p1)); - } - - /// - /// NotImplementedException with message like "Variant.GetAccessor cannot handle {0}." - /// - internal static Exception VariantGetAccessorNYI(object p0) - { - return new NotImplementedException(Strings.VariantGetAccessorNYI(p0)); + return new System.Reflection.AmbiguousMatchException(StringUtil.Format(ParserStrings.AmbiguousConversion, p0, p1)); } } - - // *** END GENERATED CODE *** - - #endregion } - diff --git a/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs b/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs index c1a937fd318..74314850a59 100644 --- a/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs +++ b/src/System.Management.Automation/engine/ComInterop/ExcepInfo.cs @@ -1,8 +1,7 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; @@ -11,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 @@ -27,7 +26,6 @@ internal struct ExcepInfo private int _scode; #if DEBUG - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2207:InitializeValueTypeStaticFieldsInline")] static ExcepInfo() { Debug.Assert(Marshal.SizeOf(typeof(ExcepInfo)) == Marshal.SizeOf(typeof(ComTypes.EXCEPINFO))); @@ -47,22 +45,6 @@ private static string ConvertAndFreeBstr(ref IntPtr bstr) return result; } - internal void Dummy() - { - _wCode = 0; - _wReserved = 0; _wReserved++; - _bstrSource = IntPtr.Zero; - _bstrDescription = IntPtr.Zero; - _bstrHelpFile = IntPtr.Zero; - _dwHelpContext = 0; - _pfnDeferredFillIn = IntPtr.Zero; - _pvReserved = IntPtr.Zero; - _scode = 0; - - throw Error.MethodShouldNotBeCalled(); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] internal Exception GetException() { Debug.Assert(_pfnDeferredFillIn == IntPtr.Zero); @@ -101,13 +83,9 @@ internal Exception GetException() { helpLink += "#" + _dwHelpContext; } - exception.HelpLink = helpLink; return exception; } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/Helpers.cs b/src/System.Management.Automation/engine/ComInterop/Helpers.cs index d29462d235c..a780e095e20 100644 --- a/src/System.Management.Automation/engine/ComInterop/Helpers.cs +++ b/src/System.Management.Automation/engine/ComInterop/Helpers.cs @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !CLR2 +#nullable enable + +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System.Runtime.CompilerServices; namespace System.Management.Automation.ComInterop { @@ -19,8 +19,35 @@ internal static Expression Convert(Expression expression, Type type) return expression; } + if (expression.Type == typeof(void)) + { + return Expression.Block(expression, Expression.Default(type)); + } + + if (type == typeof(void)) + { + return Expression.Block(expression, Expression.Empty()); + } + return Expression.Convert(expression, type); } } -} + internal static class Requires + { + [System.Diagnostics.Conditional("DEBUG")] + internal static void NotNull(object value, [CallerArgumentExpression("value")] string? paramName = null) + { + ArgumentNullException.ThrowIfNull(value, paramName); + } + + [System.Diagnostics.Conditional("DEBUG")] + internal static void Condition(bool precondition, string paramName) + { + if (!precondition) + { + throw new ArgumentException(paramName); + } + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs b/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs index bb8a2eeb5a8..7c2a3c106a7 100644 --- a/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/IDispatchComObject.cs @@ -1,48 +1,37 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Dynamic; +using System.Linq.Expressions; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using ComTypes = System.Runtime.InteropServices.ComTypes; -using System.Dynamic; namespace System.Management.Automation.ComInterop { /// - /// An object that implements IDispatch + /// A wrapper around a COM object that implements IDispatch /// /// This currently has the following issues: - /// 1. If we prefer ComObjectWithTypeInfo over IDispatchComObject, then we will often not - /// IDispatchComObject since implementations of IDispatch often rely on a registered type library. - /// If we prefer IDispatchComObject over ComObjectWithTypeInfo, users get a non-ideal experience. - /// 2. IDispatch cannot distinguish between properties and methods with 0 arguments (and non-0 + /// 1. IDispatch cannot distinguish between properties and methods with 0 arguments (and non-0 /// 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. - /// 3. IronPython processes the signature and converts ref arguments into return values. - /// However, since the signature of a DispMethod is not available beforehand, this conversion - /// is not possible. There could be other signature conversions that may be affected. How does - /// VB6 deal with ref arguments and IDispatch? /// /// We also support events for IDispatch objects: /// Background: /// COM objects support events through a mechanism known as Connect Points. /// Connection Points are separate objects created off the actual COM /// object (this is to prevent circular references between event sink - /// and event source). When clients want to sink events generated by + /// and event source). When clients want to sink events generated by /// COM object they would implement callback interfaces (aka source /// interfaces) and hand it over (advise) to the Connection Point. /// @@ -76,7 +65,7 @@ namespace System.Management.Automation.ComInterop /// source interface, we will create and advise a new ComEventSink. Each /// ComEventSink implements a single source interface that COM object /// supports. - /// 3. ComEventSink contains a map between method DISPIDs to the + /// 3. ComEventSink contains a map between method DISPIDs to the /// multicast delegate that will be invoked when the event is raised. /// 4. ComEventSink implements IReflect interface which is exposed as /// custom IDispatch to COM consumers. This allows us to intercept calls @@ -84,7 +73,6 @@ namespace System.Management.Automation.ComInterop /// just find and invoke the multicast delegate corresponding to the invoked /// dispid. /// - internal sealed class IDispatchComObject : ComObject, IDynamicMetaObjectProvider { private ComTypeDesc _comTypeDesc; @@ -111,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 @@ -140,25 +128,6 @@ private static int GetIDsOfNames(IDispatch dispatch, string name, out int dispId return hresult; } - private static int Invoke(IDispatch dispatch, int memberDispId, out object result) - { - Guid emptyRiid = Guid.Empty; - ComTypes.DISPPARAMS dispParams = new ComTypes.DISPPARAMS(); - ComTypes.EXCEPINFO excepInfo = new ComTypes.EXCEPINFO(); - uint argErr; - int hresult = dispatch.TryInvoke( - memberDispId, - ref emptyRiid, - 0, - ComTypes.INVOKEKIND.INVOKE_PROPERTYGET, - ref dispParams, - out result, - out excepInfo, - out argErr); - - return hresult; - } - internal bool TryGetGetItem(out ComMethodDesc value) { ComMethodDesc methodDesc = _comTypeDesc.GetItem; @@ -237,8 +206,7 @@ internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) { EnsureScanDefinedMethods(); - int dispId; - int hresult = GetIDsOfNames(DispatchObject, name, out dispId); + int hresult = GetIDsOfNames(DispatchObject, name, out int dispId); if (hresult == ComHresults.S_OK) { @@ -247,23 +215,21 @@ internal bool TryGetMemberMethodExplicit(string name, out ComMethodDesc method) method = cmd; return true; } - else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) + + if (hresult == ComHresults.DISP_E_UNKNOWNNAME) { method = null; return false; } - else - { - 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) { EnsureScanDefinedMethods(); - int dispId; - int hresult = GetIDsOfNames(DispatchObject, name, out dispId); + int hresult = GetIDsOfNames(DispatchObject, name, out int dispId); if (hresult == ComHresults.S_OK) { @@ -283,18 +249,16 @@ internal bool TryGetPropertySetterExplicit(string name, out ComMethodDesc method { method = putref; } - return true; } - else if (hresult == ComHresults.DISP_E_UNKNOWNNAME) + + if (hresult == ComHresults.DISP_E_UNKNOWNNAME) { method = null; return false; } - else - { - 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) @@ -305,13 +269,9 @@ internal override IList GetMemberNames(bool dataOnly) return ComTypeDesc.GetMemberNames(dataOnly); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")] internal override IList> GetMembers(IEnumerable names) { - if (names == null) - { - names = GetMemberNames(true); - } + names ??= GetMemberNames(true); Type comType = RuntimeCallableWrapper.GetType(); @@ -323,8 +283,7 @@ internal override IList> GetMembers(IEnumerable> GetMembers(IEnumerable(method.Name, value)); - // evaluation failed for some reason. pass exception out + //evaluation failed for some reason. pass exception out } catch (Exception ex) { @@ -356,10 +315,9 @@ DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) return new IDispatchMetaObject(parameter, this); } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] private static void GetFuncDescForDescIndex(ComTypes.ITypeInfo typeInfo, int funcIndex, out ComTypes.FUNCDESC funcDesc, out IntPtr funcDescHandle) { - IntPtr pFuncDesc = IntPtr.Zero; + IntPtr pFuncDesc; typeInfo.GetFuncDesc(funcIndex, out pFuncDesc); // GetFuncDesc should never return null, this is just to be safe @@ -376,13 +334,13 @@ private void EnsureScanDefinedEvents() { // _comTypeDesc.Events is null if we have not yet attempted // to scan the object for events. - if (_comTypeDesc != null && _comTypeDesc.Events != null) + if (_comTypeDesc?.Events != null) { return; } // check type info in the type descriptions cache - ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject, true); + ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject); if (typeInfo == null) { _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc(); @@ -395,7 +353,7 @@ private void EnsureScanDefinedEvents() { lock (s_cacheComTypeDesc) { - if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true && + if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) && _comTypeDesc.Events != null) { return; @@ -405,8 +363,8 @@ private void EnsureScanDefinedEvents() ComTypeDesc typeDesc = ComTypeDesc.FromITypeInfo(typeInfo, typeAttr); - ComTypes.ITypeInfo classTypeInfo = null; - Dictionary events = null; + ComTypes.ITypeInfo classTypeInfo; + Dictionary events; var cpc = RuntimeCallableWrapper as ComTypes.IConnectionPointContainer; if (cpc == null) @@ -414,7 +372,7 @@ private void EnsureScanDefinedEvents() // No ICPC - this object does not support events events = ComTypeDesc.EmptyEvents; } - else if ((classTypeInfo = GetCoClassTypeInfo(this.RuntimeCallableWrapper, typeInfo)) == null) + else if ((classTypeInfo = GetCoClassTypeInfo(RuntimeCallableWrapper, typeInfo)) == null) { // no class info found - this object may support events // but we could not discover those @@ -427,14 +385,11 @@ private void EnsureScanDefinedEvents() ComTypes.TYPEATTR classTypeAttr = ComRuntimeHelpers.GetTypeAttrForTypeInfo(classTypeInfo); for (int i = 0; i < classTypeAttr.cImplTypes; i++) { - int hRefType; - classTypeInfo.GetRefTypeOfImplType(i, out hRefType); + classTypeInfo.GetRefTypeOfImplType(i, out int hRefType); - ComTypes.ITypeInfo interfaceTypeInfo; - classTypeInfo.GetRefTypeInfo(hRefType, out interfaceTypeInfo); + classTypeInfo.GetRefTypeInfo(hRefType, out ComTypes.ITypeInfo interfaceTypeInfo); - ComTypes.IMPLTYPEFLAGS flags; - classTypeInfo.GetImplTypeFlags(i, out flags); + classTypeInfo.GetImplTypeFlags(i, out ComTypes.IMPLTYPEFLAGS flags); if ((flags & ComTypes.IMPLTYPEFLAGS.IMPLTYPEFLAG_FSOURCE) != 0) { ScanSourceInterface(interfaceTypeInfo, ref events); @@ -449,8 +404,7 @@ private void EnsureScanDefinedEvents() lock (s_cacheComTypeDesc) { - ComTypeDesc cachedTypeDesc; - if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) + if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out ComTypeDesc cachedTypeDesc)) { _comTypeDesc = cachedTypeDesc; } @@ -459,7 +413,6 @@ private void EnsureScanDefinedEvents() _comTypeDesc = typeDesc; s_cacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc); } - _comTypeDesc.Events = events; } } @@ -474,15 +427,13 @@ private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref D try { - ComTypes.FUNCDESC funcDesc; - GetFuncDescForDescIndex(sourceTypeInfo, index, out funcDesc, out funcDescHandleToRelease); + GetFuncDescForDescIndex(sourceTypeInfo, index, out ComTypes.FUNCDESC funcDesc, out funcDescHandleToRelease); // we are not interested in hidden or restricted functions for now. if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FHIDDEN) != 0) { continue; } - if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) { continue; @@ -495,11 +446,13 @@ private static void ScanSourceInterface(ComTypes.ITypeInfo sourceTypeInfo, ref D // adding new events and putting them on new interfaces while keeping the // old interfaces around. This may cause name collisions which we are // resolving by keeping only the first event with the same name. - if (events.ContainsKey(name) == false) + if (!events.ContainsKey(name)) { - ComEventDesc eventDesc = new ComEventDesc(); - eventDesc.dispid = funcDesc.memid; - eventDesc.sourceIID = sourceTypeAttribute.guid; + ComEventDesc eventDesc = new ComEventDesc + { + Dispid = funcDesc.memid, + SourceIID = sourceTypeAttribute.guid + }; events.Add(name, eventDesc); } } @@ -517,8 +470,7 @@ private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeI { Debug.Assert(typeInfo != null); - IProvideClassInfo provideClassInfo = rcw as IProvideClassInfo; - if (provideClassInfo != null) + if (rcw is IProvideClassInfo provideClassInfo) { IntPtr typeInfoPtr = IntPtr.Zero; try @@ -541,9 +493,7 @@ private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeI // retrieving class information through IPCI has failed - // we can try scanning the typelib to find the coclass - ComTypes.ITypeLib typeLib; - int typeInfoIndex; - typeInfo.GetContainingTypeLib(out typeLib, out typeInfoIndex); + typeInfo.GetContainingTypeLib(out ComTypes.ITypeLib typeLib, out int _); string typeName = ComRuntimeHelpers.GetNameOfType(typeInfo); ComTypeLibDesc typeLibDesc = ComTypeLibDesc.GetFromTypeLib(typeLib); @@ -553,20 +503,19 @@ private static ComTypes.ITypeInfo GetCoClassTypeInfo(object rcw, ComTypes.ITypeI return null; } - ComTypes.ITypeInfo typeInfoCoClass; Guid coclassGuid = coclassDesc.Guid; - typeLib.GetTypeInfoOfGuid(ref coclassGuid, out typeInfoCoClass); + typeLib.GetTypeInfoOfGuid(ref coclassGuid, out ComTypes.ITypeInfo typeInfoCoClass); return typeInfoCoClass; } private void EnsureScanDefinedMethods() { - if (_comTypeDesc != null && _comTypeDesc.Funcs != null) + if (_comTypeDesc?.Funcs != null) { return; } - ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject, true); + ComTypes.ITypeInfo typeInfo = ComRuntimeHelpers.GetITypeInfoFromIDispatch(DispatchObject); if (typeInfo == null) { _comTypeDesc = ComTypeDesc.CreateEmptyTypeDesc(); @@ -579,7 +528,7 @@ private void EnsureScanDefinedMethods() { lock (s_cacheComTypeDesc) { - if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) == true && + if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out _comTypeDesc) && _comTypeDesc.Funcs != null) { return; @@ -615,8 +564,7 @@ private void EnsureScanDefinedMethods() try { - ComTypes.FUNCDESC funcDesc; - GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out funcDesc, out funcDescHandleToRelease); + GetFuncDescForDescIndex(typeInfo, definedFuncIndex, out ComTypes.FUNCDESC funcDesc, out funcDescHandleToRelease); if ((funcDesc.wFuncFlags & (int)ComTypes.FUNCFLAGS.FUNCFLAG_FRESTRICTED) != 0) { @@ -625,7 +573,7 @@ private void EnsureScanDefinedMethods() } ComMethodDesc method = new ComMethodDesc(typeInfo, funcDesc); - string name = method.Name.ToUpper(System.Globalization.CultureInfo.InvariantCulture); + string name = method.Name.ToUpper(CultureInfo.InvariantCulture); if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUT) != 0) { @@ -644,10 +592,8 @@ private void EnsureScanDefinedMethods() { setItem = method; } - continue; } - if ((funcDesc.invkind & ComTypes.INVOKEKIND.INVOKE_PROPERTYPUTREF) != 0) { // If there is a getter for this put, use that ReturnType as the @@ -664,7 +610,6 @@ private void EnsureScanDefinedMethods() { setItem = method; } - continue; } @@ -706,8 +651,7 @@ private void EnsureScanDefinedMethods() lock (s_cacheComTypeDesc) { - ComTypeDesc cachedTypeDesc; - if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out cachedTypeDesc)) + if (s_cacheComTypeDesc.TryGetValue(typeAttr.guid, out ComTypeDesc cachedTypeDesc)) { _comTypeDesc = cachedTypeDesc; } @@ -716,7 +660,6 @@ private void EnsureScanDefinedMethods() _comTypeDesc = typeDesc; s_cacheComTypeDesc.Add(typeAttr.guid, _comTypeDesc); } - _comTypeDesc.Funcs = funcs; _comTypeDesc.Puts = puts; _comTypeDesc.PutRefs = putrefs; @@ -734,14 +677,9 @@ internal bool TryGetPropertySetter(string name, out ComMethodDesc method, Type l return _comTypeDesc.TryGetPut(name, out method) || _comTypeDesc.TryGetPutRef(name, out method); } - else - { - return _comTypeDesc.TryGetPutRef(name, out method) || - _comTypeDesc.TryGetPut(name, out method); - } + + return _comTypeDesc.TryGetPutRef(name, out method) || + _comTypeDesc.TryGetPut(name, out method); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs index 9a4c9693c27..9826ac9d467 100644 --- a/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/IDispatchMetaObject.cs @@ -1,17 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Linq; +using System; using System.Collections.Generic; using System.Dynamic; -using System.Runtime.InteropServices.ComTypes; +using System.Linq; +using System.Linq.Expressions; using System.Management.Automation.Language; +using System.Runtime.InteropServices.ComTypes; namespace System.Management.Automation.ComInterop { @@ -27,6 +23,8 @@ internal IDispatchMetaObject(Expression expression, IDispatchComObject self) public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { + Requires.NotNull(binder); + ComMethodDesc method = null; // See if this is actually a property set @@ -65,8 +63,9 @@ public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, Dy public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) { - ComMethodDesc method; - if (_self.TryGetGetItem(out method)) + Requires.NotNull(binder); + + if (_self.TryGetGetItem(out ComMethodDesc method)) { List temps = new List(); List initTemps = new List(); @@ -89,12 +88,12 @@ private DynamicMetaObject BindComInvoke(DynamicMetaObject[] args, ComMethodDesc Expression.Constant(method), Expression.Property( Helpers.Convert(Expression, typeof(IDispatchComObject)), - typeof(IDispatchComObject).GetProperty("DispatchObject") + typeof(IDispatchComObject).GetProperty(nameof(IDispatchComObject.DispatchObject)) ), method ).Invoke(); - if ((temps != null) && (temps.Any())) + if (temps != null && temps.Count > 0) { Expression invokeExpression = invoke.Expression; Expression call = Expression.Block(invokeExpression.Type, temps, initTemps.Append(invokeExpression)); @@ -107,13 +106,12 @@ private DynamicMetaObject BindComInvoke(DynamicMetaObject[] args, ComMethodDesc public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { ComBinder.ComGetMemberBinder comBinder = binder as ComBinder.ComGetMemberBinder; - bool canReturnCallables = comBinder == null ? false : comBinder._CanReturnCallables; + bool canReturnCallables = comBinder?._canReturnCallables ?? false; - ComMethodDesc method; - ComEventDesc @event; + Requires.NotNull(binder); // 1. Try methods - if (_self.TryGetMemberMethod(binder.Name, out method)) + if (_self.TryGetMemberMethod(binder.Name, out ComMethodDesc method)) { if (((method.InvokeKind & INVOKEKIND.INVOKE_PROPERTYGET) == INVOKEKIND.INVOKE_PROPERTYGET) && @@ -124,7 +122,7 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) } // 2. Try events - if (_self.TryGetMemberEvent(binder.Name, out @event)) + if (_self.TryGetMemberEvent(binder.Name, out ComEventDesc @event)) { return BindEvent(@event); } @@ -162,7 +160,7 @@ private DynamicMetaObject BindGetMember(ComMethodDesc method, bool canReturnCall return new DynamicMetaObject( Expression.Call( - typeof(ComRuntimeHelpers).GetMethod("CreateDispCallable"), + typeof(ComRuntimeHelpers).GetMethod(nameof(ComRuntimeHelpers.CreateDispCallable)), Helpers.Convert(Expression, typeof(IDispatchComObject)), Expression.Constant(method) ), @@ -170,15 +168,15 @@ private DynamicMetaObject BindGetMember(ComMethodDesc method, bool canReturnCall ); } - private DynamicMetaObject BindEvent(ComEventDesc @event) + private DynamicMetaObject BindEvent(ComEventDesc eventDesc) { // BoundDispEvent CreateComEvent(object rcw, Guid sourceIid, int dispid) Expression result = Expression.Call( - typeof(ComRuntimeHelpers).GetMethod("CreateComEvent"), + typeof(ComRuntimeHelpers).GetMethod(nameof(ComRuntimeHelpers.CreateComEvent)), ComObject.RcwFromComObject(Expression), - Expression.Constant(@event.sourceIID), - Expression.Constant(@event.dispid) + Expression.Constant(eventDesc.SourceIID), + Expression.Constant(eventDesc.Dispid) ); return new DynamicMetaObject( @@ -189,8 +187,9 @@ private DynamicMetaObject BindEvent(ComEventDesc @event) public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) { - ComMethodDesc getItem; - if (_self.TryGetGetItem(out getItem)) + Requires.NotNull(binder); + + if (_self.TryGetGetItem(out ComMethodDesc getItem)) { List temps = new List(); List initTemps = new List(); @@ -204,8 +203,9 @@ public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMet public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) { - ComMethodDesc setItem; - if (_self.TryGetSetItem(out setItem)) + Requires.NotNull(binder); + + if (_self.TryGetSetItem(out ComMethodDesc setItem)) { List temps = new List(); List initTemps = new List(); @@ -214,12 +214,15 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet isByRef = isByRef.AddLast(false); // Convert the value to the target type - DynamicMetaObject updatedValue = new DynamicMetaObject(value.CastOrConvertMethodArgument( - value.LimitType, - setItem.Name, - "SetIndex", - temps, - initTemps), value.Restrictions); + DynamicMetaObject updatedValue = new DynamicMetaObject( + value.CastOrConvertMethodArgument( + value.LimitType, + setItem.Name, + "SetIndex", + allowCastingToByRefLikeType: false, + temps, + initTemps), + value.Restrictions); var result = BindComInvoke(indexes.AddLast(updatedValue), setItem, binder.CallInfo, isByRef, temps, initTemps); @@ -235,6 +238,8 @@ public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMet public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) { + Requires.NotNull(binder); + return // 1. Check for simple property put TryPropertyPut(binder, value) ?? @@ -248,19 +253,18 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM private DynamicMetaObject TryPropertyPut(SetMemberBinder binder, DynamicMetaObject value) { - ComMethodDesc method; bool holdsNull = value.Value == null && value.HasValue; - if (_self.TryGetPropertySetter(binder.Name, out method, value.LimitType, holdsNull) || + if (_self.TryGetPropertySetter(binder.Name, out ComMethodDesc method, value.LimitType, holdsNull) || _self.TryGetPropertySetterExplicit(binder.Name, out method, value.LimitType, holdsNull)) { BindingRestrictions restrictions = IDispatchRestriction(); Expression dispatch = Expression.Property( Helpers.Convert(Expression, typeof(IDispatchComObject)), - typeof(IDispatchComObject).GetProperty("DispatchObject") + typeof(IDispatchComObject).GetProperty(nameof(IDispatchComObject.DispatchObject)) ); - var result = new ComInvokeBinder( + DynamicMetaObject result = new ComInvokeBinder( new CallInfo(1), new[] { value }, new bool[] { false }, @@ -282,8 +286,7 @@ private DynamicMetaObject TryPropertyPut(SetMemberBinder binder, DynamicMetaObje private DynamicMetaObject TryEventHandlerNoop(SetMemberBinder binder, DynamicMetaObject value) { - ComEventDesc @event; - if (_self.TryGetMemberEvent(binder.Name, out @event) && value.LimitType == typeof(BoundDispEvent)) + if (_self.TryGetMemberEvent(binder.Name, out _) && value.LimitType == typeof(BoundDispEvent)) { // Drop the event property set. return new DynamicMetaObject( @@ -309,7 +312,7 @@ internal static BindingRestrictions IDispatchRestriction(Expression expr, ComTyp Expression.Equal( Expression.Property( Helpers.Convert(expr, typeof(IDispatchComObject)), - typeof(IDispatchComObject).GetProperty("ComTypeDesc") + typeof(IDispatchComObject).GetProperty(nameof(IDispatchComObject.ComTypeDesc)) ), Expression.Constant(typeDesc) ) @@ -327,6 +330,3 @@ protected override ComUnwrappedMetaObject UnwrapSelf() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/IPseudoComObject.cs b/src/System.Management.Automation/engine/ComInterop/IPseudoComObject.cs index 9ab6c1aae65..08617e10e06 100644 --- a/src/System.Management.Automation/engine/ComInterop/IPseudoComObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/IPseudoComObject.cs @@ -1,12 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Dynamic; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -15,4 +11,3 @@ internal interface IPseudoComObject DynamicMetaObject GetMetaObject(Expression expression); } } - diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs new file mode 100644 index 00000000000..f8ff1e09855 --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsMethod.cs @@ -0,0 +1,274 @@ +// 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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace System.Management.Automation.InteropServices +{ + /// + /// Part of ComEventHelpers APIs which allow binding + /// managed delegates to COM's connection point based events. + /// + internal class ComEventsMethod + { + /// + /// This delegate wrapper class handles dynamic invocation of delegates. The reason for the wrapper's + /// existence is that under certain circumstances we need to coerce arguments to types expected by the + /// delegates signature. Normally, reflection (Delegate.DynamicInvoke) handles type coercion + /// correctly but one known case is when the expected signature is 'ref Enum' - in this case + /// reflection by design does not do the coercion. Since we need to be compatible with COM interop + /// handling of this scenario - we are pre-processing delegate's signature by looking for 'ref enums' + /// and cache the types required for such coercion. + /// + public class DelegateWrapper + { + private bool _once; + private int _expectedParamsCount; + private Type?[]? _cachedTargetTypes; + + public DelegateWrapper(Delegate d, bool wrapArgs) + { + Delegate = d; + WrapArgs = wrapArgs; + } + + public Delegate Delegate { get; set; } + + public bool WrapArgs { get; } + + public object? Invoke(object[] args) + { + if (Delegate == null) + { + return null; + } + + if (!_once) + { + PreProcessSignature(); + _once = true; + } + + if (_cachedTargetTypes != null && _expectedParamsCount == args.Length) + { + for (int i = 0; i < _expectedParamsCount; i++) + { + if (_cachedTargetTypes[i] != null) + { + args[i] = Enum.ToObject(_cachedTargetTypes[i]!, args[i]); // TODO-NULLABLE: Indexer nullability tracked (https://github.com/dotnet/roslyn/issues/34644) + } + } + } + + return Delegate.DynamicInvoke(WrapArgs ? new object[] { args } : args); + } + + private void PreProcessSignature() + { + ParameterInfo[] parameters = Delegate.Method.GetParameters(); + _expectedParamsCount = parameters.Length; + + Type?[]? targetTypes = null; + for (int i = 0; i < _expectedParamsCount; i++) + { + ParameterInfo pi = parameters[i]; + + // recognize only 'ref Enum' signatures and cache + // both enum type and the underlying type. + if (pi.ParameterType.IsByRef + && pi.ParameterType.HasElementType + && pi.ParameterType.GetElementType()!.IsEnum) + { + targetTypes ??= new Type?[_expectedParamsCount]; + + targetTypes[i] = pi.ParameterType.GetElementType(); + } + } + + if (targetTypes != null) + { + _cachedTargetTypes = targetTypes; + } + } + } + + /// + /// Invoking ComEventsMethod means invoking a multi-cast delegate attached to it. + /// Since multicast delegate's built-in chaining supports only chaining instances of the same type, + /// we need to complement this design by using an explicit linked list data structure. + /// + private readonly List _delegateWrappers = new List(); + + private readonly int _dispid; + private ComEventsMethod? _next; + + public ComEventsMethod(int dispid) + { + _dispid = dispid; + } + + public static ComEventsMethod? Find(ComEventsMethod? methods, int dispid) + { + while (methods != null && methods._dispid != dispid) + { + methods = methods._next; + } + + return methods; + } + + public static ComEventsMethod Add(ComEventsMethod? methods, ComEventsMethod method) + { + method._next = methods; + return method; + } + + public static ComEventsMethod? Remove(ComEventsMethod methods, ComEventsMethod method) + { + Debug.Assert(methods != null, "removing method from empty methods collection"); + Debug.Assert(method != null, "specify method is null"); + + if (methods == method) + { + return methods._next; + } + else + { + ComEventsMethod? current = methods; + + while (current != null && current._next != method) + { + current = current._next; + } + + if (current != null) + { + current._next = method._next; + } + + return methods; + } + } + + public bool Empty + { + get + { + lock (_delegateWrappers) + { + return _delegateWrappers.Count == 0; + } + } + } + + public void AddDelegate(Delegate d, bool wrapArgs = false) + { + lock (_delegateWrappers) + { + // Update an existing delegate wrapper + foreach (DelegateWrapper wrapper in _delegateWrappers) + { + if (wrapper.Delegate.GetType() == d.GetType() && wrapper.WrapArgs == wrapArgs) + { + wrapper.Delegate = Delegate.Combine(wrapper.Delegate, d); + return; + } + } + + var newWrapper = new DelegateWrapper(d, wrapArgs); + _delegateWrappers.Add(newWrapper); + } + } + + public void RemoveDelegate(Delegate d, bool wrapArgs = false) + { + lock (_delegateWrappers) + { + // Find delegate wrapper index + int removeIdx = -1; + DelegateWrapper? wrapper = null; + for (int i = 0; i < _delegateWrappers.Count; i++) + { + DelegateWrapper wrapperMaybe = _delegateWrappers[i]; + if (wrapperMaybe.Delegate.GetType() == d.GetType() && wrapperMaybe.WrapArgs == wrapArgs) + { + removeIdx = i; + wrapper = wrapperMaybe; + break; + } + } + + if (removeIdx < 0) + { + // Not present in collection + return; + } + + // Update wrapper or remove from collection + Delegate? newDelegate = Delegate.Remove(wrapper!.Delegate, d); + if (newDelegate != null) + { + wrapper.Delegate = newDelegate; + } + else + { + _delegateWrappers.RemoveAt(removeIdx); + } + } + } + + public void RemoveDelegates(Func condition) + { + lock (_delegateWrappers) + { + // Find delegate wrapper indexes. Iterate in reverse such that the list to remove is sorted by high to low index. + List toRemove = new List(); + for (int i = _delegateWrappers.Count - 1; i >= 0; i--) + { + DelegateWrapper wrapper = _delegateWrappers[i]; + Delegate[] invocationList = wrapper.Delegate.GetInvocationList(); + foreach (Delegate delegateMaybe in invocationList) + { + if (condition(delegateMaybe)) + { + Delegate? newDelegate = Delegate.Remove(wrapper!.Delegate, delegateMaybe); + if (newDelegate != null) + { + wrapper.Delegate = newDelegate; + } + else + { + toRemove.Add(i); + } + } + } + } + + foreach (int idx in toRemove) + { + _delegateWrappers.RemoveAt(idx); + } + } + } + + public object? Invoke(object[] args) + { + Debug.Assert(!Empty); + object? result = null; + + lock (_delegateWrappers) + { + foreach (DelegateWrapper wrapper in _delegateWrappers) + { + result = wrapper.Invoke(args); + } + } + + return result; + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsSink.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsSink.cs new file mode 100644 index 00000000000..4197a392815 --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/ComEventsSink.cs @@ -0,0 +1,285 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; +using ComTypes = System.Runtime.InteropServices.ComTypes; + +namespace System.Management.Automation.InteropServices +{ + /// + /// Part of ComEventHelpers APIs which allow binding + /// managed delegates to COM's connection point based events. + /// + internal partial class ComEventsSink : IDispatch, ICustomQueryInterface + { + private Guid _iidSourceItf; + private ComTypes.IConnectionPoint? _connectionPoint; + private int _cookie; + private ComEventsMethod? _methods; + private ComEventsSink? _next; + + public ComEventsSink(object rcw, Guid iid) + { + _iidSourceItf = iid; + this.Advise(rcw); + } + + public static ComEventsSink? Find(ComEventsSink? sinks, ref Guid iid) + { + ComEventsSink? sink = sinks; + while (sink != null && sink._iidSourceItf != iid) + { + sink = sink._next; + } + + return sink; + } + + public static ComEventsSink Add(ComEventsSink? sinks, ComEventsSink sink) + { + sink._next = sinks; + return sink; + } + + public static ComEventsSink? RemoveAll(ComEventsSink? sinks) + { + while (sinks != null) + { + sinks.Unadvise(); + sinks = sinks._next; + } + + return null; + } + + public static ComEventsSink? Remove(ComEventsSink sinks, ComEventsSink sink) + { + Debug.Assert(sinks != null, "removing event sink from empty sinks collection"); + Debug.Assert(sink != null, "specify event sink is null"); + + ComEventsSink? toReturn = sinks; + + if (sink == sinks) + { + toReturn = sinks._next; + } + else + { + ComEventsSink? current = sinks; + while (current != null && current._next != sink) + { + current = current._next; + } + + if (current != null) + { + current._next = sink._next; + } + } + + sink.Unadvise(); + + return toReturn; + } + + public ComEventsMethod? RemoveMethod(ComEventsMethod method) + { + _methods = ComEventsMethod.Remove(_methods!, method); + return _methods; + } + + public ComEventsMethod? FindMethod(int dispid) + { + return ComEventsMethod.Find(_methods, dispid); + } + + public ComEventsMethod AddMethod(int dispid) + { + ComEventsMethod method = new ComEventsMethod(dispid); + _methods = ComEventsMethod.Add(_methods, method); + return method; + } + + int IDispatch.GetTypeInfoCount() + { + return 0; + } + + ComTypes.ITypeInfo IDispatch.GetTypeInfo(int iTInfo, int lcid) + { + throw new NotImplementedException(); + } + + void IDispatch.GetIDsOfNames(ref Guid iid, string[] names, int cNames, int lcid, int[] rgDispId) + { + throw new NotImplementedException(); + } + + private const VarEnum VT_BYREF_VARIANT = VarEnum.VT_BYREF | VarEnum.VT_VARIANT; + private const VarEnum VT_TYPEMASK = (VarEnum)0x0fff; + private const VarEnum VT_BYREF_TYPEMASK = VT_TYPEMASK | VarEnum.VT_BYREF; + + private static unsafe ref Variant GetVariant(ref Variant pSrc) + { + if (pSrc.VariantType == VT_BYREF_VARIANT) + { + // For VB6 compatibility reasons, if the VARIANT is a VT_BYREF | VT_VARIANT that + // contains another VARIANT with VT_BYREF | VT_VARIANT, then we need to extract the + // inner VARIANT and use it instead of the outer one. Note that if the inner VARIANT + // is VT_BYREF | VT_VARIANT | VT_ARRAY, it will pass the below test too. + Span pByRefVariant = new Span(pSrc.AsByRefVariant.ToPointer(), 1); + if ((pByRefVariant[0].VariantType & VT_BYREF_TYPEMASK) == VT_BYREF_VARIANT) + { + return ref pByRefVariant[0]; + } + } + + return ref pSrc; + } + + unsafe void IDispatch.Invoke( + int dispid, + ref Guid riid, + int lcid, + InvokeFlags wFlags, + ref ComTypes.DISPPARAMS pDispParams, + IntPtr pVarResult, + IntPtr pExcepInfo, + IntPtr puArgErr) + { + ComEventsMethod? method = FindMethod(dispid); + if (method == null) + { + return; + } + + // notice the unsafe pointers we are using. This is to avoid unnecessary + // arguments marshalling. see code:ComEventsHelper#ComEventsArgsMarshalling + + const int InvalidIdx = -1; + object[] args = new object[pDispParams.cArgs]; + int[] byrefsMap = new int[pDispParams.cArgs]; + bool[] usedArgs = new bool[pDispParams.cArgs]; + + int totalCount = pDispParams.cNamedArgs + pDispParams.cArgs; + var vars = new Span(pDispParams.rgvarg.ToPointer(), totalCount); + var namedArgs = new Span(pDispParams.rgdispidNamedArgs.ToPointer(), totalCount); + + // copy the named args (positional) as specified + int i; + int pos; + for (i = 0; i < pDispParams.cNamedArgs; i++) + { + pos = namedArgs[i]; + ref Variant pvar = ref GetVariant(ref vars[i]); + args[pos] = pvar.ToObject()!; + usedArgs[pos] = true; + + int byrefIdx = InvalidIdx; + if (pvar.IsByRef) + { + byrefIdx = i; + } + + byrefsMap[pos] = byrefIdx; + } + + // copy the rest of the arguments in the reverse order + pos = 0; + for (; i < pDispParams.cArgs; i++) + { + // find the next unassigned argument + while (usedArgs[pos]) + { + pos++; + } + + ref Variant pvar = ref GetVariant(ref vars[pDispParams.cArgs - 1 - i]); + args[pos] = pvar.ToObject()!; + + int byrefIdx = InvalidIdx; + if (pvar.IsByRef) + { + byrefIdx = pDispParams.cArgs - 1 - i; + } + + byrefsMap[pos] = byrefIdx; + + pos++; + } + + // Do the actual delegate invocation + object? result = method.Invoke(args); + + // convert result to VARIANT + if (pVarResult != IntPtr.Zero) + { + Marshal.GetNativeVariantForObject(result, pVarResult); + } + + // Now we need to marshal all the byrefs back + for (i = 0; i < pDispParams.cArgs; i++) + { + int idxToPos = byrefsMap[i]; + if (idxToPos == InvalidIdx) + { + continue; + } + + ref Variant pvar = ref GetVariant(ref vars[idxToPos]); + pvar.CopyFromIndirect(args[i]); + } + } + + CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv) + { + ppv = IntPtr.Zero; + if (iid == _iidSourceItf || iid == typeof(IDispatch).GUID) + { + ppv = Marshal.GetComInterfaceForObject(this, typeof(IDispatch), CustomQueryInterfaceMode.Ignore); + return CustomQueryInterfaceResult.Handled; + } + + return CustomQueryInterfaceResult.NotHandled; + } + + private void Advise(object rcw) + { + Debug.Assert(_connectionPoint == null, "COM event sink is already advised"); + + ComTypes.IConnectionPointContainer cpc = (ComTypes.IConnectionPointContainer)rcw; + ComTypes.IConnectionPoint cp; + cpc.FindConnectionPoint(ref _iidSourceItf, out cp!); + + object sinkObject = this; + cp.Advise(sinkObject, out _cookie); + + _connectionPoint = cp; + } + + private void Unadvise() + { + Debug.Assert(_connectionPoint != null, "Can not unadvise from empty connection point"); + if (_connectionPoint == null) + return; + + try + { + _connectionPoint.Unadvise(_cookie); + Marshal.ReleaseComObject(_connectionPoint); + } + catch + { + // swallow all exceptions on unadvise + // the host may not be available at this point + } + finally + { + _connectionPoint = null; + } + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/IDispatch.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/IDispatch.cs new file mode 100644 index 00000000000..be32cdd3f5a --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/IDispatch.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; +using ComTypes = System.Runtime.InteropServices.ComTypes; + +namespace System.Management.Automation.InteropServices +{ + [ComImport] + [Guid("00020400-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IDispatch + { + int GetTypeInfoCount(); + + ComTypes.ITypeInfo GetTypeInfo( + int iTInfo, + int lcid); + + void GetIDsOfNames( + ref Guid riid, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 2), In] + string[] rgszNames, + int cNames, + int lcid, + [Out] int[] rgDispId); + + // The last 3 parameters of Invoke() are optional and must be defined + // as IntPtr in C#, since there is no language feature for optional ref/out. + void Invoke( + int dispIdMember, + ref Guid riid, + int lcid, + InvokeFlags wFlags, + ref ComTypes.DISPPARAMS pDispParams, + /* out/optional */ IntPtr pVarResult, + /* out/optional */ IntPtr pExcepInfo, + /* out/optional */ IntPtr puArgErr); + } + + [Flags] + internal enum InvokeFlags : short + { + DISPATCH_METHOD = 1, + DISPATCH_PROPERTYGET = 2, + DISPATCH_PROPERTYPUT = 4, + DISPATCH_PROPERTYPUTREF = 8 + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs b/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs new file mode 100644 index 00000000000..941f471b666 --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/InteropServices/Variant.cs @@ -0,0 +1,732 @@ +// 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.Diagnostics; +using System.Runtime.InteropServices; + +namespace System.Management.Automation.InteropServices +{ + /// + /// Variant is the basic COM type for late-binding. It can contain any other COM data type. + /// This type definition precisely matches the unmanaged data layout so that the struct can be passed + /// to and from COM calls. + /// + [StructLayout(LayoutKind.Explicit)] + internal partial struct Variant + { +#if DEBUG + static Variant() + { + // Variant size is the size of 4 pointers (16 bytes) on a 32-bit processor, + // and 3 pointers (24 bytes) on a 64-bit processor. + int variantSize = Marshal.SizeOf(typeof(Variant)); + if (IntPtr.Size == 4) + { + Debug.Assert(variantSize == (4 * IntPtr.Size)); + } + else + { + Debug.Assert(IntPtr.Size == 8); + Debug.Assert(variantSize == (3 * IntPtr.Size)); + } + } +#endif + + // Most of the data types in the Variant are carried in _typeUnion + [FieldOffset(0)] private TypeUnion _typeUnion; + + // Decimal is the largest data type and it needs to use the space that is normally unused in TypeUnion._wReserved1, etc. + // Hence, it is declared to completely overlap with TypeUnion. A Decimal does not use the first two bytes, and so + // TypeUnion._vt can still be used to encode the type. + [FieldOffset(0)] private decimal _decimal; + + [StructLayout(LayoutKind.Sequential)] + private struct TypeUnion + { + public ushort _vt; + public ushort _wReserved1; + public ushort _wReserved2; + public ushort _wReserved3; + + public UnionTypes _unionTypes; + } + + [StructLayout(LayoutKind.Sequential)] + private struct Record + { + public IntPtr _record; + public IntPtr _recordInfo; + } + + [StructLayout(LayoutKind.Explicit)] + private struct UnionTypes + { + [FieldOffset(0)] public sbyte _i1; + [FieldOffset(0)] public short _i2; + [FieldOffset(0)] public int _i4; + [FieldOffset(0)] public long _i8; + [FieldOffset(0)] public byte _ui1; + [FieldOffset(0)] public ushort _ui2; + [FieldOffset(0)] public uint _ui4; + [FieldOffset(0)] public ulong _ui8; + [FieldOffset(0)] public int _int; + [FieldOffset(0)] public uint _uint; + [FieldOffset(0)] public short _bool; + [FieldOffset(0)] public int _error; + [FieldOffset(0)] public float _r4; + [FieldOffset(0)] public double _r8; + [FieldOffset(0)] public long _cy; + [FieldOffset(0)] public double _date; + [FieldOffset(0)] public IntPtr _bstr; + [FieldOffset(0)] public IntPtr _unknown; + [FieldOffset(0)] public IntPtr _dispatch; + [FieldOffset(0)] public IntPtr _pvarVal; + [FieldOffset(0)] public IntPtr _byref; + [FieldOffset(0)] public Record _record; + } + + /// + /// Primitive types are the basic COM types. It includes valuetypes like ints, but also reference types + /// like BStrs. It does not include composite types like arrays and user-defined COM types (IUnknown/IDispatch). + /// + public static bool IsPrimitiveType(VarEnum varEnum) + { + switch (varEnum) + { + case VarEnum.VT_I1: + case VarEnum.VT_I2: + case VarEnum.VT_I4: + case VarEnum.VT_I8: + case VarEnum.VT_UI1: + case VarEnum.VT_UI2: + case VarEnum.VT_UI4: + case VarEnum.VT_UI8: + case VarEnum.VT_INT: + case VarEnum.VT_UINT: + case VarEnum.VT_BOOL: + case VarEnum.VT_ERROR: + case VarEnum.VT_R4: + case VarEnum.VT_R8: + case VarEnum.VT_DECIMAL: + case VarEnum.VT_CY: + case VarEnum.VT_DATE: + case VarEnum.VT_BSTR: + return true; + } + + return false; + } + + public unsafe void CopyFromIndirect(object value) + { + VarEnum vt = (VarEnum)(((int)this.VariantType) & ~((int)VarEnum.VT_BYREF)); + + if (value == null) + { + if (vt == VarEnum.VT_DISPATCH || vt == VarEnum.VT_UNKNOWN || vt == VarEnum.VT_BSTR) + { + *(IntPtr*)this._typeUnion._unionTypes._byref = IntPtr.Zero; + } + return; + } + + if ((vt & VarEnum.VT_ARRAY) != 0) + { + Variant vArray; + Marshal.GetNativeVariantForObject(value, (IntPtr)(void*)&vArray); + *(IntPtr*)this._typeUnion._unionTypes._byref = vArray._typeUnion._unionTypes._byref; + return; + } + + switch (vt) + { + case VarEnum.VT_I1: + *(sbyte*)this._typeUnion._unionTypes._byref = (sbyte)value; + break; + + case VarEnum.VT_UI1: + *(byte*)this._typeUnion._unionTypes._byref = (byte)value; + break; + + case VarEnum.VT_I2: + *(short*)this._typeUnion._unionTypes._byref = (short)value; + break; + + case VarEnum.VT_UI2: + *(ushort*)this._typeUnion._unionTypes._byref = (ushort)value; + break; + + case VarEnum.VT_BOOL: + // VARIANT_TRUE = -1 + // VARIANT_FALSE = 0 + *(short*)this._typeUnion._unionTypes._byref = (bool)value ? (short)-1 : (short)0; + break; + + case VarEnum.VT_I4: + case VarEnum.VT_INT: + *(int*)this._typeUnion._unionTypes._byref = (int)value; + break; + + case VarEnum.VT_UI4: + case VarEnum.VT_UINT: + *(uint*)this._typeUnion._unionTypes._byref = (uint)value; + break; + + case VarEnum.VT_ERROR: + *(int*)this._typeUnion._unionTypes._byref = ((ErrorWrapper)value).ErrorCode; + break; + + case VarEnum.VT_I8: + *(long*)this._typeUnion._unionTypes._byref = (long)value; + break; + + case VarEnum.VT_UI8: + *(ulong*)this._typeUnion._unionTypes._byref = (ulong)value; + break; + + case VarEnum.VT_R4: + *(float*)this._typeUnion._unionTypes._byref = (float)value; + break; + + case VarEnum.VT_R8: + *(double*)this._typeUnion._unionTypes._byref = (double)value; + break; + + case VarEnum.VT_DATE: + *(double*)this._typeUnion._unionTypes._byref = ((DateTime)value).ToOADate(); + break; + + case VarEnum.VT_UNKNOWN: + *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.GetIUnknownForObject(value); + break; + + case VarEnum.VT_DISPATCH: + *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.GetComInterfaceForObject(value); + break; + + case VarEnum.VT_BSTR: + *(IntPtr*)this._typeUnion._unionTypes._byref = Marshal.StringToBSTR((string)value); + break; + + case VarEnum.VT_CY: + *(long*)this._typeUnion._unionTypes._byref = decimal.ToOACurrency((decimal)value); + break; + + case VarEnum.VT_DECIMAL: + *(decimal*)this._typeUnion._unionTypes._byref = (decimal)value; + break; + + case VarEnum.VT_VARIANT: + Marshal.GetNativeVariantForObject(value, this._typeUnion._unionTypes._byref); + break; + + default: + throw new ArgumentException(); + } + } + + /// + /// Get the managed object representing the Variant. + /// + /// + public object? ToObject() + { + // Check the simple case upfront + if (IsEmpty) + { + return null; + } + + switch (VariantType) + { + case VarEnum.VT_NULL: + return DBNull.Value; + + case VarEnum.VT_I1: return AsI1; + case VarEnum.VT_I2: return AsI2; + case VarEnum.VT_I4: return AsI4; + case VarEnum.VT_I8: return AsI8; + case VarEnum.VT_UI1: return AsUi1; + case VarEnum.VT_UI2: return AsUi2; + case VarEnum.VT_UI4: return AsUi4; + case VarEnum.VT_UI8: return AsUi8; + case VarEnum.VT_INT: return AsInt; + case VarEnum.VT_UINT: return AsUint; + case VarEnum.VT_BOOL: return AsBool; + case VarEnum.VT_ERROR: return AsError; + case VarEnum.VT_R4: return AsR4; + case VarEnum.VT_R8: return AsR8; + case VarEnum.VT_DECIMAL: return AsDecimal; + case VarEnum.VT_CY: return AsCy; + case VarEnum.VT_DATE: return AsDate; + case VarEnum.VT_BSTR: return AsBstr; + case VarEnum.VT_UNKNOWN: return AsUnknown; + case VarEnum.VT_DISPATCH: return AsDispatch; + + default: + unsafe + { + fixed (void* pThis = &this) + { + return Marshal.GetObjectForNativeVariant((System.IntPtr)pThis); + } + } + } + } + + /// + /// Release any unmanaged memory associated with the Variant + /// + public void Clear() + { + // We do not need to call OLE32's VariantClear for primitive types or ByRefs + // to save ourselves the cost of interop transition. + // ByRef indicates the memory is not owned by the VARIANT itself while + // primitive types do not have any resources to free up. + // Hence, only safearrays, BSTRs, interfaces and user types are + // handled differently. + VarEnum vt = VariantType; + if ((vt & VarEnum.VT_BYREF) != 0) + { + VariantType = VarEnum.VT_EMPTY; + } + else if (((vt & VarEnum.VT_ARRAY) != 0) + || (vt == VarEnum.VT_BSTR) + || (vt == VarEnum.VT_UNKNOWN) + || (vt == VarEnum.VT_DISPATCH) + || (vt == VarEnum.VT_VARIANT) + || (vt == VarEnum.VT_RECORD)) + { + unsafe + { + fixed (void* pThis = &this) + { + Interop.Windows.VariantClear((nint)pThis); + } + } + + Debug.Assert(IsEmpty); + } + else + { + VariantType = VarEnum.VT_EMPTY; + } + } + + public VarEnum VariantType + { + get => (VarEnum)_typeUnion._vt; + set => _typeUnion._vt = (ushort)value; + } + + public bool IsEmpty => _typeUnion._vt == ((ushort)VarEnum.VT_EMPTY); + + public bool IsByRef => (_typeUnion._vt & ((ushort)VarEnum.VT_BYREF)) != 0; + + public void SetAsNULL() + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_NULL; + } + + // VT_I1 + + public sbyte AsI1 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_I1); + return _typeUnion._unionTypes._i1; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_I1; + _typeUnion._unionTypes._i1 = value; + } + } + + // VT_I2 + + public short AsI2 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_I2); + return _typeUnion._unionTypes._i2; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_I2; + _typeUnion._unionTypes._i2 = value; + } + } + + // VT_I4 + + public int AsI4 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_I4); + return _typeUnion._unionTypes._i4; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_I4; + _typeUnion._unionTypes._i4 = value; + } + } + + // VT_I8 + + public long AsI8 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_I8); + return _typeUnion._unionTypes._i8; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_I8; + _typeUnion._unionTypes._i8 = value; + } + } + + // VT_UI1 + + public byte AsUi1 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UI1); + return _typeUnion._unionTypes._ui1; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UI1; + _typeUnion._unionTypes._ui1 = value; + } + } + + // VT_UI2 + + public ushort AsUi2 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UI2); + return _typeUnion._unionTypes._ui2; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UI2; + _typeUnion._unionTypes._ui2 = value; + } + } + + // VT_UI4 + + public uint AsUi4 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UI4); + return _typeUnion._unionTypes._ui4; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UI4; + _typeUnion._unionTypes._ui4 = value; + } + } + + // VT_UI8 + + public ulong AsUi8 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UI8); + return _typeUnion._unionTypes._ui8; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UI8; + _typeUnion._unionTypes._ui8 = value; + } + } + + // VT_INT + + public int AsInt + { + get + { + Debug.Assert(VariantType == VarEnum.VT_INT); + return _typeUnion._unionTypes._int; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_INT; + _typeUnion._unionTypes._int = value; + } + } + + // VT_UINT + + public uint AsUint + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UINT); + return _typeUnion._unionTypes._uint; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UINT; + _typeUnion._unionTypes._uint = value; + } + } + + // VT_BOOL + + public bool AsBool + { + get + { + Debug.Assert(VariantType == VarEnum.VT_BOOL); + return _typeUnion._unionTypes._bool != 0; + } + + set + { + Debug.Assert(IsEmpty); + // VARIANT_TRUE = -1 + // VARIANT_FALSE = 0 + VariantType = VarEnum.VT_BOOL; + _typeUnion._unionTypes._bool = value ? (short)-1 : (short)0; + } + } + + // VT_ERROR + + public int AsError + { + get + { + Debug.Assert(VariantType == VarEnum.VT_ERROR); + return _typeUnion._unionTypes._error; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_ERROR; + _typeUnion._unionTypes._error = value; + } + } + + // VT_R4 + + public float AsR4 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_R4); + return _typeUnion._unionTypes._r4; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_R4; + _typeUnion._unionTypes._r4 = value; + } + } + + // VT_R8 + + public double AsR8 + { + get + { + Debug.Assert(VariantType == VarEnum.VT_R8); + return _typeUnion._unionTypes._r8; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_R8; + _typeUnion._unionTypes._r8 = value; + } + } + + // VT_DECIMAL + + public decimal AsDecimal + { + get + { + Debug.Assert(VariantType == VarEnum.VT_DECIMAL); + // The first byte of Decimal is unused, but usually set to 0 + Variant v = this; + v._typeUnion._vt = 0; + return v._decimal; + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_DECIMAL; + _decimal = value; + // _vt overlaps with _decimal, and should be set after setting _decimal + _typeUnion._vt = (ushort)VarEnum.VT_DECIMAL; + } + } + + // VT_CY + + public decimal AsCy + { + get + { + Debug.Assert(VariantType == VarEnum.VT_CY); + return decimal.FromOACurrency(_typeUnion._unionTypes._cy); + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_CY; + _typeUnion._unionTypes._cy = decimal.ToOACurrency(value); + } + } + + // VT_DATE + + public DateTime AsDate + { + get + { + Debug.Assert(VariantType == VarEnum.VT_DATE); + return DateTime.FromOADate(_typeUnion._unionTypes._date); + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_DATE; + _typeUnion._unionTypes._date = value.ToOADate(); + } + } + + // VT_BSTR + + public string AsBstr + { + get + { + Debug.Assert(VariantType == VarEnum.VT_BSTR); + return (string)Marshal.PtrToStringBSTR(this._typeUnion._unionTypes._bstr); + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_BSTR; + this._typeUnion._unionTypes._bstr = Marshal.StringToBSTR(value); + } + } + + // VT_UNKNOWN + + public object? AsUnknown + { + get + { + Debug.Assert(VariantType == VarEnum.VT_UNKNOWN); + if (_typeUnion._unionTypes._unknown == IntPtr.Zero) + { + return null; + } + return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._unknown); + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_UNKNOWN; + if (value == null) + { + _typeUnion._unionTypes._unknown = IntPtr.Zero; + } + else + { + _typeUnion._unionTypes._unknown = Marshal.GetIUnknownForObject(value); + } + } + } + + // VT_DISPATCH + + public object? AsDispatch + { + get + { + Debug.Assert(VariantType == VarEnum.VT_DISPATCH); + if (_typeUnion._unionTypes._dispatch == IntPtr.Zero) + { + return null; + } + return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._dispatch); + } + + set + { + Debug.Assert(IsEmpty); + VariantType = VarEnum.VT_DISPATCH; + if (value == null) + { + _typeUnion._unionTypes._dispatch = IntPtr.Zero; + } + else + { + _typeUnion._unionTypes._dispatch = Marshal.GetComInterfaceForObject(value); + } + } + } + + public IntPtr AsByRefVariant + { + get + { + Debug.Assert(VariantType == (VarEnum.VT_BYREF | VarEnum.VT_VARIANT)); + return _typeUnion._unionTypes._pvarVal; + } + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/NullArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/NullArgBuilder.cs index d5747f6fc68..994cb207537 100644 --- a/src/System.Management.Automation/engine/ComInterop/NullArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/NullArgBuilder.cs @@ -1,12 +1,7 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif namespace System.Management.Automation.ComInterop { @@ -23,6 +18,3 @@ internal override Expression Marshal(Expression parameter) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/README.md b/src/System.Management.Automation/engine/ComInterop/README.md new file mode 100644 index 00000000000..0431129bf82 --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/README.md @@ -0,0 +1,69 @@ +# ComInterop + +The ComInterop code shipped in PowerShell comes from [dotnet/runtime](https://github.com/dotnet/runtime) with _a considerable amount of refactoring work_ to make it work properly with PowerShell. + +> **NOTE: Do not modify the ComInterop code unless for fixing a bug. We want to keep minimal diffs when comparing with the .NET 5.0 ComInterop code base.** + +There are 3 sources of the ComInterop code as our references: + +1. [The .NET Framework version](https://github.com/IronLanguages/main/tree/ipy-2.7-maint/Runtime/Microsoft.Dynamic/ComInterop). + The code was archived in 2012. + +2. [The .NET 5.0 version](https://github.com/dotnet/runtime/tree/master/src/libraries/Microsoft.CSharp/src/Microsoft/CSharp/RuntimeBinder/ComInterop). + It was merged into .NET 5.0 in May 2020 through the PR [dotnet/runtime#33060](https://github.com/dotnet/runtime/pull/33060). + It's based on the .NET Framework version code with quite amount of refactoring work. + +3. [The legacy ComInterop code from Windows PowerShell](https://github.com/PowerShell/PowerShell/tree/v7.0.0/src/System.Management.Automation/engine/ComInterop). + The legacy code has always been in the repository, but it was excluded from compilation. + It was based on the .NET Framework version code with a considerable amount of refactoring work to make it work properly with Windows PowerShell. + +## Code Refreshing + +The ComInterop code was refreshed and enabled in compilation in PowerShell 7.1, August 2020. +It was done manually by: + +- A careful **three-way comparison** across all the code sources listed above: + - Compare (3) to (1) to get the PowerShell specific changes + - Compare (2) to (1) to get the .NET 5.0 changes + +- Applying the PowerShell specific changes from (3) to (2), with necessary further refactoring + +- A careful review of the new PowerShell specific changes applied to (2), again using the three-way comparison: + - Compare the refreshed ComInterop code with (3) to get the differences + - Analyze the differences to make sure they are either the pure .NET 5.0 changes over the .NET Framework code, + or the refactoring changes that is necessary to apply the PowerShell specific changes. + +## Code Layout Changes + +The major changes in code layout are recorded below. + +### Removed Source Files + +The following source files that existed in (1) and (3) were removed in (2): + +```none +ComDispIds.cs +ComEventSink.cs +ComEventSinkProxy.cs +ComParamDesc.cs +ComType.cs +ComTypeLibInfo.cs +ComTypeLibMemberDesc.cs +TypeLibInfoMetaObject.cs +``` + +I examined them all and it was mainly a clean-up work done in .NET 5.0: + +- The code in `ComParamDesc.cs`, `ComType.cs`, `ComTypeLibInfo.cs`, `ComTypeLibMemberDesc.cs` and `TypeLibInfoMetaObject.cs` + are not used or not needed, and thus removed. +- The code in `ComDispIds.cs`, `ComEventSink.cs` and `ComEventSinkProxy.cs` were moved or refactored to different files. + +### New Source Files + +Some source files were refactored and moved to the `System.Runtime.InteropServices` namespace in .NET 5.0, +they can be found [here](https://github.com/dotnet/runtime/tree/master/src/libraries/Common/src/System/Runtime/InteropServices). +In PowerShell, the corresponding source files are placed under `engine\ComInterop\InteropServices`. + +Additionally, `ComEventsSink.Extended.cs` and `Variant.Extended.cs` under `engine\ComInterop` are new in .NET 5.0. +Combined with the files `ComEventsSink.cs` and `Variant.cs` under `engine\ComInterop\InteropServices`, +they are the refactored replacement of the old `ComEventSink.cs`, `ComEventSinkProxy.cs` and `Variant.cs` files in .NET Framework. diff --git a/src/System.Management.Automation/engine/ComInterop/SimpleArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/SimpleArgBuilder.cs index 1df83daf12b..5dad442afe9 100644 --- a/src/System.Management.Automation/engine/ComInterop/SimpleArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/SimpleArgBuilder.cs @@ -1,13 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Diagnostics; +using System.Linq.Expressions; namespace System.Management.Automation.ComInterop { @@ -23,7 +19,7 @@ internal SimpleArgBuilder(Type parameterType) ParameterType = parameterType; } - internal Type ParameterType { get; } + protected Type ParameterType { get; } internal override Expression Marshal(Expression parameter) { @@ -39,6 +35,3 @@ internal override Expression UnmarshalFromRef(Expression newValue) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs b/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs index 07ad10f6bac..926bb460a09 100644 --- a/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs +++ b/src/System.Management.Automation/engine/ComInterop/SplatCallSite.cs @@ -1,26 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !CLR2 -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Diagnostics; using System.Runtime.CompilerServices; -//using Microsoft.Scripting.Utils; -#if !SILVERLIGHT namespace System.Management.Automation.ComInterop { internal sealed class SplatCallSite { - // Stored callable Delegate or IDynamicMetaObjectProvider. + // Stored callable IDynamicMetaObjectProvider. internal readonly object _callable; // Can the number of arguments to a given event change each call? // If not, we don't need this level of indirection--we could cache a // delegate that does the splatting. - internal CallSite> _site; + private CallSite> _site; internal SplatCallSite(object callable) { @@ -28,26 +23,16 @@ internal SplatCallSite(object callable) _callable = callable; } + public delegate object InvokeDelegate(object[] args); + internal object Invoke(object[] args) { Debug.Assert(args != null); - // If it is a delegate, just let DynamicInvoke do the binding. - var d = _callable as Delegate; - if (d != null) - { - return d.DynamicInvoke(args); - } - - // Otherwise, create a CallSite and invoke it. - if (_site == null) - { - _site = CallSite>.Create(SplatInvokeBinder.Instance); - } + // Create a CallSite and invoke it. + _site ??= CallSite>.Create(SplatInvokeBinder.Instance); return _site.Target(_site, _callable, args); } } } -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/StringArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/StringArgBuilder.cs index 437407baf7d..69bad228cc2 100644 --- a/src/System.Management.Automation/engine/ComInterop/StringArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/StringArgBuilder.cs @@ -1,14 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 +using System; +using System.Diagnostics; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Runtime.InteropServices; -using System.Diagnostics; namespace System.Management.Automation.ComInterop { @@ -19,9 +15,7 @@ internal class StringArgBuilder : SimpleArgBuilder internal StringArgBuilder(Type parameterType) : base(parameterType) { - Debug.Assert(parameterType == typeof(string) || - parameterType == typeof(BStrWrapper)); - + Debug.Assert(parameterType == typeof(string) || parameterType == typeof(BStrWrapper)); _isWrapper = parameterType == typeof(BStrWrapper); } @@ -34,9 +28,9 @@ internal override Expression Marshal(Expression parameter) { parameter = Expression.Property( Helpers.Convert(parameter, typeof(BStrWrapper)), - typeof(BStrWrapper).GetProperty("WrappedObject") + typeof(BStrWrapper).GetProperty(nameof(BStrWrapper.WrappedObject)) ); - }; + } return parameter; } @@ -47,7 +41,7 @@ internal override Expression MarshalToRef(Expression parameter) // Marshal.StringToBSTR(parameter) return Expression.Call( - typeof(Marshal).GetMethod("StringToBSTR"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.StringToBSTR)), parameter ); } @@ -59,7 +53,7 @@ internal override Expression UnmarshalFromRef(Expression value) Expression.Equal(value, Expression.Constant(IntPtr.Zero)), Expression.Constant(null, typeof(string)), // default value Expression.Call( - typeof(Marshal).GetMethod("PtrToStringBSTR"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.PtrToStringBSTR)), value ) ); @@ -70,12 +64,9 @@ internal override Expression UnmarshalFromRef(Expression value) typeof(BStrWrapper).GetConstructor(new Type[] { typeof(string) }), unmarshal ); - }; + } return base.UnmarshalFromRef(unmarshal); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/TypeEnumMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/TypeEnumMetaObject.cs index 4a07e275836..934eee7294e 100644 --- a/src/System.Management.Automation/engine/ComInterop/TypeEnumMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/TypeEnumMetaObject.cs @@ -1,16 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections.Generic; using System.Dynamic; -//using Microsoft.Scripting.Runtime; -//using AstUtils = Microsoft.Scripting.Ast.Utils; +using System.Linq.Expressions; using AstUtils = System.Management.Automation.Interpreter.Utils; namespace System.Management.Automation.ComInterop @@ -55,9 +49,9 @@ private BindingRestrictions EnumRestrictions() Expression.Property( Expression.Property( AstUtils.Convert(Expression, typeof(ComTypeEnumDesc)), - typeof(ComTypeDesc).GetProperty("TypeLib")), - typeof(ComTypeLibDesc).GetProperty("Guid")), - AstUtils.Constant(_desc.TypeLib.Guid) + typeof(ComTypeDesc).GetProperty(nameof(ComTypeDesc.TypeLib))), + typeof(ComTypeLibDesc).GetProperty(nameof(ComTypeLibDesc.Guid))), + Expression.Constant(_desc.TypeLib.Guid) ) ) ).Merge( @@ -65,15 +59,12 @@ private BindingRestrictions EnumRestrictions() Expression.Equal( Expression.Property( AstUtils.Convert(Expression, typeof(ComTypeEnumDesc)), - typeof(ComTypeEnumDesc).GetProperty("TypeName") + typeof(ComTypeEnumDesc).GetProperty(nameof(ComTypeEnumDesc.TypeName)) ), - AstUtils.Constant(_desc.TypeName) + Expression.Constant(_desc.TypeName) ) ) ); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/TypeLibInfoMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/TypeLibInfoMetaObject.cs deleted file mode 100644 index 88035265236..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/TypeLibInfoMetaObject.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif - -using System.Collections.Generic; -using System.Dynamic; -//using Microsoft.Scripting.Utils; -//using AstUtils = Microsoft.Scripting.Ast.Utils; -using AstUtils = System.Management.Automation.Interpreter.Utils; - -namespace System.Management.Automation.ComInterop -{ - internal sealed class TypeLibInfoMetaObject : DynamicMetaObject - { - private readonly ComTypeLibInfo _info; - - internal TypeLibInfoMetaObject(Expression expression, ComTypeLibInfo info) - : base(expression, BindingRestrictions.Empty, info) - { - _info = info; - } - - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) - { - string name = binder.Name; - - if (name == _info.Name) - { - name = "TypeLibDesc"; - } - else if (name != "Guid" && - name != "Name" && - name != "VersionMajor" && - name != "VersionMinor") - { - return binder.FallbackGetMember(this); - } - - return new DynamicMetaObject( - Expression.Convert( - Expression.Property( - AstUtils.Convert(Expression, typeof(ComTypeLibInfo)), - typeof(ComTypeLibInfo).GetProperty(name) - ), - typeof(object) - ), - ComTypeLibInfoRestrictions(this) - ); - } - - public override IEnumerable GetDynamicMemberNames() - { - return _info.GetMemberNames(); - } - - private BindingRestrictions ComTypeLibInfoRestrictions(params DynamicMetaObject[] args) - { - return BindingRestrictions.Combine(args).Merge(BindingRestrictions.GetTypeRestriction(Expression, typeof(ComTypeLibInfo))); - } - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/TypeLibMetaObject.cs b/src/System.Management.Automation/engine/ComInterop/TypeLibMetaObject.cs index a4eab6f7ad5..b3433e0b046 100644 --- a/src/System.Management.Automation/engine/ComInterop/TypeLibMetaObject.cs +++ b/src/System.Management.Automation/engine/ComInterop/TypeLibMetaObject.cs @@ -1,16 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Dynamic; -//using AstUtils = Microsoft.Scripting.Ast.Utils; +using System.Linq.Expressions; using AstUtils = System.Management.Automation.Interpreter.Utils; namespace System.Management.Automation.ComInterop @@ -39,15 +32,15 @@ private DynamicMetaObject TryBindGetMember(string name) AstUtils.Convert( Expression, typeof(ComTypeLibDesc) ), - typeof(ComTypeLibDesc).GetProperty("Guid") + typeof(ComTypeLibDesc).GetProperty(nameof(ComTypeLibDesc.Guid)) ), - AstUtils.Constant(_lib.Guid) + Expression.Constant(_lib.Guid) ) ) ); return new DynamicMetaObject( - AstUtils.Constant( + Expression.Constant( ((ComTypeLibDesc)Value).GetTypeLibObjectDesc(name) ), restrictions @@ -64,7 +57,7 @@ public override DynamicMetaObject BindGetMember(GetMemberBinder binder) public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) { - var result = TryBindGetMember(binder.Name); + DynamicMetaObject result = TryBindGetMember(binder.Name); if (result != null) { return binder.FallbackInvoke(result, args, null); @@ -79,6 +72,3 @@ public override IEnumerable GetDynamicMemberNames() } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs b/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs index ab82f854f28..04217bd0bb6 100644 --- a/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs +++ b/src/System.Management.Automation/engine/ComInterop/TypeUtils.cs @@ -1,37 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !CLR2 -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Reflection; namespace System.Management.Automation.ComInterop { internal static class TypeUtils { - private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - internal const MethodAttributes PublicStatic = MethodAttributes.Public | MethodAttributes.Static; - - // CONFORMING + //CONFORMING internal static Type GetNonNullableType(Type type) { if (IsNullableType(type)) { return type.GetGenericArguments()[0]; } - return type; } - // CONFORMING + //CONFORMING internal static bool IsNullableType(this Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } - // CONFORMING + //CONFORMING internal static bool AreReferenceAssignable(Type dest, Type src) { // WARNING: This actually implements "Is this identity assignable and/or reference assignable?" @@ -39,32 +32,28 @@ internal static bool AreReferenceAssignable(Type dest, Type src) { return true; } - if (!dest.IsValueType && !src.IsValueType && AreAssignable(dest, src)) { return true; } - return false; } - // CONFORMING + + //CONFORMING internal static bool AreAssignable(Type dest, Type src) { if (dest == src) { return true; } - if (dest.IsAssignableFrom(src)) { return true; } - if (dest.IsArray && src.IsArray && dest.GetArrayRank() == src.GetArrayRank() && AreReferenceAssignable(dest.GetElementType(), src.GetElementType())) { return true; } - if (src.IsArray && dest.IsGenericType && (dest.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IEnumerable<>) || dest.GetGenericTypeDefinition() == typeof(System.Collections.Generic.IList<>) @@ -73,11 +62,10 @@ internal static bool AreAssignable(Type dest, Type src) { return true; } - return false; } - // CONFORMING + //CONFORMING internal static bool IsImplicitlyConvertible(Type source, Type destination) { return IsIdentityConversion(source, destination) || @@ -92,7 +80,7 @@ internal static bool IsImplicitlyConvertible(Type source, Type destination, bool (considerUserDefined && GetUserDefinedCoercionMethod(source, destination, true) != null); } - // CONFORMING + //CONFORMING internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type convertToType, bool implicitOnly) { // check for implicit coercions first @@ -105,7 +93,6 @@ internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type c { return method; } - MethodInfo[] cMethods = nnConvType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); method = FindConversionOperator(cMethods, convertFrom, convertToType, implicitOnly); if (method != null) @@ -115,18 +102,18 @@ internal static MethodInfo GetUserDefinedCoercionMethod(Type convertFrom, Type c // try lifted conversion if (nnExprType != convertFrom || nnConvType != convertToType) { - method = FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly) ?? - FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly); + method = + FindConversionOperator(eMethods, nnExprType, nnConvType, implicitOnly) ?? + FindConversionOperator(cMethods, nnExprType, nnConvType, implicitOnly); if (method != null) { return method; } } - return null; } - // CONFORMING + //CONFORMING internal static MethodInfo FindConversionOperator(MethodInfo[] methods, Type typeFrom, Type typeTo, bool implicitOnly) { foreach (MethodInfo mi in methods) @@ -140,18 +127,16 @@ internal static MethodInfo FindConversionOperator(MethodInfo[] methods, Type typ continue; return mi; } - return null; } - // CONFORMING + //CONFORMING private static bool IsIdentityConversion(Type source, Type destination) { return source == destination; } - // CONFORMING - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + //CONFORMING private static bool IsImplicitNumericConversion(Type source, Type destination) { TypeCode tcSource = Type.GetTypeCode(source); @@ -170,7 +155,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Byte: switch (tcDest) @@ -186,7 +170,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Int16: switch (tcDest) @@ -198,7 +181,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.UInt16: switch (tcDest) @@ -212,7 +194,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Int32: switch (tcDest) @@ -223,7 +204,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.UInt32: switch (tcDest) @@ -235,7 +215,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Int64: case TypeCode.UInt64: @@ -246,7 +225,6 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Char: switch (tcDest) @@ -261,22 +239,20 @@ private static bool IsImplicitNumericConversion(Type source, Type destination) case TypeCode.Decimal: return true; } - return false; case TypeCode.Single: return (tcDest == TypeCode.Double); } - return false; } - // CONFORMING + //CONFORMING private static bool IsImplicitReferenceConversion(Type source, Type destination) { return AreAssignable(destination, source); } - // CONFORMING + //CONFORMING private static bool IsImplicitBoxingConversion(Type source, Type destination) { if (source.IsValueType && (destination == typeof(object) || destination == typeof(System.ValueType))) @@ -287,4 +263,3 @@ private static bool IsImplicitBoxingConversion(Type source, Type destination) } } } - diff --git a/src/System.Management.Automation/engine/ComInterop/UnknownArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/UnknownArgBuilder.cs index 552e262c859..cf97a976a80 100644 --- a/src/System.Management.Automation/engine/ComInterop/UnknownArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/UnknownArgBuilder.cs @@ -1,13 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject - -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -31,9 +26,9 @@ internal override Expression Marshal(Expression parameter) { parameter = Expression.Property( Helpers.Convert(parameter, typeof(UnknownWrapper)), - typeof(UnknownWrapper).GetProperty("WrappedObject") + typeof(UnknownWrapper).GetProperty(nameof(UnknownWrapper.WrappedObject)) ); - }; + } return Helpers.Convert(parameter, typeof(object)); } @@ -47,7 +42,7 @@ internal override Expression MarshalToRef(Expression parameter) Expression.Equal(parameter, Expression.Constant(null)), Expression.Constant(IntPtr.Zero), Expression.Call( - typeof(Marshal).GetMethod("GetIUnknownForObject"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.GetIUnknownForObject)), parameter ) ); @@ -60,7 +55,7 @@ internal override Expression UnmarshalFromRef(Expression value) Expression.Equal(value, Expression.Constant(IntPtr.Zero)), Expression.Constant(null), Expression.Call( - typeof(Marshal).GetMethod("GetObjectForIUnknown"), + typeof(Marshal).GetMethod(nameof(System.Runtime.InteropServices.Marshal.GetObjectForIUnknown)), value ) ); @@ -71,12 +66,9 @@ internal override Expression UnmarshalFromRef(Expression value) typeof(UnknownWrapper).GetConstructor(new Type[] { typeof(object) }), unmarshal ); - }; + } return base.UnmarshalFromRef(unmarshal); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs b/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs index 4a5bbe746a1..a9e1594ae1c 100644 --- a/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs +++ b/src/System.Management.Automation/engine/ComInterop/VarEnumSelector.cs @@ -1,11 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject +#pragma warning disable 618 // The *Wrapper classes for COM are obsolete +using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Management.Automation.InteropServices; using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -13,14 +15,14 @@ namespace System.Management.Automation.ComInterop /// /// If a managed user type (as opposed to a primitive type or a COM object) is passed as an argument to a COM call, we need /// to determine the VarEnum type we will marshal it as. We have the following options: - /// 1.Raise an exception. Languages with their own version of primitive types would not be able to call - /// COM methods using the language's types (for eg. strings in IronRuby are not System.String). An explicit - /// cast would be needed. - /// 2.We could marshal it as VT_DISPATCH. Then COM code will be able to access all the APIs in a late-bound manner, - /// but old COM components will probably malfunction if they expect a primitive type. - /// 3.We could guess which primitive type is the closest match. This will make COM components be as easily - /// accessible as .NET methods. - /// 4.We could use the type library to check what the expected type is. However, the type library may not be available. + /// 1. Raise an exception. Languages with their own version of primitive types would not be able to call + /// COM methods using the language's types (for eg. strings in IronRuby are not System.String). An explicit + /// cast would be needed. + /// 2. We could marshal it as VT_DISPATCH. Then COM code will be able to access all the APIs in a late-bound manner, + /// but old COM components will probably malfunction if they expect a primitive type. + /// 3. We could guess which primitive type is the closest match. This will make COM components be as easily + /// accessible as .NET methods. + /// 4. We could use the type library to check what the expected type is. However, the type library may not be available. /// /// VarEnumSelector implements option # 3. /// @@ -96,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 @@ -140,38 +142,29 @@ internal static Type GetManagedMarshalType(VarEnum varEnum) private static Dictionary CreateComToManagedPrimitiveTypes() { - Dictionary dict = new Dictionary(); - - #region Generated Outer ComToManagedPrimitiveTypes - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ComToManagedPrimitiveTypes from: generate_comdispatch.py - - dict[VarEnum.VT_I1] = typeof(sbyte); - dict[VarEnum.VT_I2] = typeof(Int16); - dict[VarEnum.VT_I4] = typeof(Int32); - dict[VarEnum.VT_I8] = typeof(Int64); - dict[VarEnum.VT_UI1] = typeof(byte); - dict[VarEnum.VT_UI2] = typeof(UInt16); - dict[VarEnum.VT_UI4] = typeof(UInt32); - dict[VarEnum.VT_UI8] = typeof(UInt64); - dict[VarEnum.VT_INT] = typeof(Int32); - dict[VarEnum.VT_UINT] = typeof(UInt32); - dict[VarEnum.VT_PTR] = typeof(IntPtr); - dict[VarEnum.VT_BOOL] = typeof(bool); - dict[VarEnum.VT_R4] = typeof(Single); - dict[VarEnum.VT_R8] = typeof(double); - dict[VarEnum.VT_DECIMAL] = typeof(decimal); - dict[VarEnum.VT_DATE] = typeof(DateTime); - dict[VarEnum.VT_BSTR] = typeof(string); - dict[VarEnum.VT_CLSID] = typeof(Guid); - - // *** END GENERATED CODE *** - - #endregion - - dict[VarEnum.VT_CY] = typeof(CurrencyWrapper); - dict[VarEnum.VT_ERROR] = typeof(ErrorWrapper); + Dictionary dict = new Dictionary() + { + { VarEnum.VT_I1, typeof(sbyte) }, + { VarEnum.VT_I2, typeof(Int16) }, + { VarEnum.VT_I4, typeof(Int32) }, + { VarEnum.VT_I8, typeof(Int64) }, + { VarEnum.VT_UI1, typeof(byte) }, + { VarEnum.VT_UI2, typeof(UInt16) }, + { VarEnum.VT_UI4, typeof(UInt32) }, + { VarEnum.VT_UI8, typeof(UInt64) }, + { VarEnum.VT_INT, typeof(Int32) }, + { VarEnum.VT_UINT, typeof(UInt32) }, + { VarEnum.VT_PTR, typeof(IntPtr) }, + { VarEnum.VT_BOOL, typeof(bool) }, + { VarEnum.VT_R4, typeof(float) }, + { VarEnum.VT_R8, typeof(double) }, + { VarEnum.VT_DECIMAL, typeof(decimal) }, + { VarEnum.VT_DATE, typeof(DateTime) }, + { VarEnum.VT_BSTR, typeof(string) }, + { VarEnum.VT_CLSID, typeof(Guid) }, + { VarEnum.VT_CY, typeof(CurrencyWrapper) }, + { VarEnum.VT_ERROR, typeof(ErrorWrapper) }, + }; return dict; } @@ -218,12 +211,11 @@ 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; } } } - return compatibleComTypes; } @@ -250,7 +242,6 @@ private static void CheckForAmbiguousMatch(Type argumentType, List comp { typeNames += ", "; } - typeNames += typeName; } @@ -259,11 +250,6 @@ private static void CheckForAmbiguousMatch(Type argumentType, List comp private static bool TryGetPrimitiveComType(Type argumentType, out VarEnum primitiveVarEnum) { - #region Generated Outer Managed To COM Primitive Type Map - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ManagedToComPrimitiveTypes from: generate_comdispatch.py - switch (Type.GetTypeCode(argumentType)) { case TypeCode.Boolean: @@ -327,20 +313,16 @@ private static bool TryGetPrimitiveComType(Type argumentType, out VarEnum primit if (argumentType == typeof(IntPtr)) { - primitiveVarEnum = VarEnum.VT_PTR; + primitiveVarEnum = VarEnum.VT_INT; return true; } if (argumentType == typeof(UIntPtr)) { - primitiveVarEnum = VarEnum.VT_PTR; + primitiveVarEnum = VarEnum.VT_UINT; return true; } - // *** END GENERATED CODE *** - - #endregion - primitiveVarEnum = VarEnum.VT_VOID; // error return false; } @@ -370,17 +352,17 @@ 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)) { - // actual variant type will be VT_ERROR | E_PARAMNOTFOUND + //actual variant type will be VT_ERROR | E_PARAMNOTFOUND return VarEnum.VT_RECORD; } if (argumentType.IsArray) { - // actual variant type will be VT_ARRAY | VT_ + //actual variant type will be VT_ARRAY | VT_ return VarEnum.VT_ARRAY; } @@ -388,23 +370,28 @@ private VarEnum GetComType(ref Type argumentType) { return VarEnum.VT_UNKNOWN; } - else if (argumentType == typeof(DispatchWrapper)) + + if (argumentType == typeof(DispatchWrapper)) { return VarEnum.VT_DISPATCH; } - else if (argumentType == typeof(VariantWrapper)) + + if (argumentType == typeof(VariantWrapper)) { return VarEnum.VT_VARIANT; } - else if (argumentType == typeof(BStrWrapper)) + + if (argumentType == typeof(BStrWrapper)) { return VarEnum.VT_BSTR; } - else if (argumentType == typeof(ErrorWrapper)) + + if (argumentType == typeof(ErrorWrapper)) { return VarEnum.VT_ERROR; } - else if (argumentType == typeof(CurrencyWrapper)) + + if (argumentType == typeof(CurrencyWrapper)) { return VarEnum.VT_CY; } @@ -420,20 +407,19 @@ private VarEnum GetComType(ref Type argumentType) // COM cannot express valuetype nulls so we will convert to underlying type // it will throw if there is no value - if (TypeUtils.IsNullableType(argumentType)) + if (argumentType.IsNullableType()) { argumentType = TypeUtils.GetNonNullableType(argumentType); return GetComType(ref argumentType); } - // generic types cannot be exposed to COM so they do not implement COM interfaces. + //generic types cannot be exposed to COM so they do not implement COM interfaces. if (argumentType.IsGenericType) { return VarEnum.VT_UNKNOWN; } - VarEnum primitiveVarEnum; - if (TryGetPrimitiveComType(argumentType, out primitiveVarEnum)) + if (TryGetPrimitiveComType(argumentType, out VarEnum primitiveVarEnum)) { return primitiveVarEnum; } @@ -443,12 +429,12 @@ 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 + //argumentType is coming from MarshalType, null means the dynamic object holds + //a null value and not byref if (argumentType == null) { return new VariantBuilder(VarEnum.VT_EMPTY, new NullArgBuilder()); @@ -468,9 +454,9 @@ private VariantBuilder GetVariantBuilder(Type argumentType) VarEnum elementVarEnum; if (elementType == typeof(object) || elementType == typeof(DBNull)) { - // no meaningful value to pass ByRef. - // perhaps the calee will replace it with something. - // need to pass as a variant reference + //no meaningful value to pass ByRef. + //perhaps the callee will replace it with something. + //need to pass as a variant reference elementVarEnum = VarEnum.VT_VARIANT; } else @@ -493,25 +479,23 @@ private VariantBuilder GetVariantBuilder(Type argumentType) // attempts to find marshalling type failed private static ArgBuilder GetByValArgBuilder(Type elementType, ref VarEnum elementVarEnum) { - // if VT indicates that marshalling type is unknown + // If VT indicates that marshalling type is unknown. if (elementVarEnum == VT_DEFAULT) { - // trying to find a conversion. - VarEnum convertibleTo; - if (TryGetPrimitiveComTypeViaConversion(elementType, out convertibleTo)) + // Trying to find a conversion. + if (TryGetPrimitiveComTypeViaConversion(elementType, out VarEnum convertibleTo)) { elementVarEnum = convertibleTo; Type marshalType = GetManagedMarshalType(elementVarEnum); return new ConversionArgBuilder(elementType, GetSimpleArgBuilder(marshalType, elementVarEnum)); } - // checking for IConvertible. + // Checking for IConvertible. if (typeof(IConvertible).IsAssignableFrom(elementType)) { return new ConvertibleArgBuilder(); } } - return GetSimpleArgBuilder(elementType, elementVarEnum); } @@ -549,7 +533,7 @@ private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum el argBuilder = new ErrorArgBuilder(elementType); break; default: - var marshalType = GetManagedMarshalType(elementVarEnum); + Type marshalType = GetManagedMarshalType(elementVarEnum); if (elementType == marshalType) { argBuilder = new SimpleArgBuilder(elementType); @@ -558,7 +542,6 @@ private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum el { argBuilder = new ConvertArgBuilder(elementType, marshalType); } - break; } @@ -566,6 +549,3 @@ private static SimpleArgBuilder GetSimpleArgBuilder(Type elementType, VarEnum el } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs b/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs new file mode 100644 index 00000000000..9cab38d0773 --- /dev/null +++ b/src/System.Management.Automation/engine/ComInterop/Variant.Extended.cs @@ -0,0 +1,320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using System.Management.Automation.ComInterop; + +namespace System.Management.Automation.InteropServices +{ + internal partial struct Variant + { + // VT_I1 + + public void SetAsByrefI1(ref sbyte value) + { + SetAsByref(ref value, VarEnum.VT_I1); + } + + // VT_I2 + + public void SetAsByrefI2(ref short value) + { + SetAsByref(ref value, VarEnum.VT_I2); + } + + // VT_I4 + + public void SetAsByrefI4(ref int value) + { + SetAsByref(ref value, VarEnum.VT_I4); + } + + // VT_I8 + + public void SetAsByrefI8(ref long value) + { + SetAsByref(ref value, VarEnum.VT_I8); + } + + // VT_UI1 + + public void SetAsByrefUi1(ref byte value) + { + SetAsByref(ref value, VarEnum.VT_UI1); + } + + // VT_UI2 + + public void SetAsByrefUi2(ref ushort value) + { + SetAsByref(ref value, VarEnum.VT_UI2); + } + + // VT_UI4 + + public void SetAsByrefUi4(ref uint value) + { + SetAsByref(ref value, VarEnum.VT_UI4); + } + + // VT_UI8 + + public void SetAsByrefUi8(ref ulong value) + { + SetAsByref(ref value, VarEnum.VT_UI8); + } + + // VT_INT + + public void SetAsByrefInt(ref int value) + { + SetAsByref(ref value, VarEnum.VT_INT); + } + + // VT_UINT + + public void SetAsByrefUint(ref uint value) + { + SetAsByref(ref value, VarEnum.VT_UINT); + } + + // VT_BOOL + + public void SetAsByrefBool(ref short value) + { + SetAsByref(ref value, VarEnum.VT_BOOL); + } + + // VT_ERROR + + public void SetAsByrefError(ref int value) + { + SetAsByref(ref value, VarEnum.VT_ERROR); + } + + // VT_R4 + + public void SetAsByrefR4(ref float value) + { + SetAsByref(ref value, VarEnum.VT_R4); + } + + // VT_R8 + + public void SetAsByrefR8(ref double value) + { + SetAsByref(ref value, VarEnum.VT_R8); + } + + // VT_DECIMAL + + public void SetAsByrefDecimal(ref decimal value) + { + SetAsByref(ref value, VarEnum.VT_DECIMAL); + } + + // VT_CY + + public void SetAsByrefCy(ref long value) + { + SetAsByref(ref value, VarEnum.VT_CY); + } + + // VT_DATE + + public void SetAsByrefDate(ref double value) + { + SetAsByref(ref value, VarEnum.VT_DATE); + } + + // VT_BSTR + + public void SetAsByrefBstr(ref IntPtr value) + { + SetAsByref(ref value, VarEnum.VT_BSTR); + } + + // VT_UNKNOWN + + public void SetAsByrefUnknown(ref IntPtr value) + { + SetAsByref(ref value, VarEnum.VT_UNKNOWN); + } + + // VT_DISPATCH + + public void SetAsByrefDispatch(ref IntPtr value) + { + SetAsByref(ref value, VarEnum.VT_DISPATCH); + } + + // VT_VARIANT + + public object AsVariant + { + get + { + return Marshal.GetObjectForNativeVariant(UnsafeMethods.ConvertVariantByrefToPtr(ref this)); + } + + set + { + Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise + if (value != null) + { + UnsafeMethods.InitVariantForObject(value, ref this); + } + } + } + + public void SetAsByrefVariant(ref Variant value) + { + SetAsByref(ref value, VarEnum.VT_VARIANT); + } + + // constructs a ByRef variant to pass contents of another variant ByRef. + public unsafe void SetAsByrefVariantIndirect(ref Variant value) + { + Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise + Debug.Assert((value.VariantType & VarEnum.VT_BYREF) == 0, "double indirection"); + + switch (value.VariantType) + { + case VarEnum.VT_EMPTY: + case VarEnum.VT_NULL: + // these cannot combine with VT_BYREF. Should try passing as a variant reference + SetAsByrefVariant(ref value); + return; + case VarEnum.VT_RECORD: + // VT_RECORD's are weird in that regardless of is the VT_BYREF flag is set or not + // they have the same internal representation. + _typeUnion._unionTypes._record = value._typeUnion._unionTypes._record; + break; + case VarEnum.VT_DECIMAL: + _typeUnion._unionTypes._byref = (IntPtr)Unsafe.AsPointer(ref value._decimal); + break; + default: + _typeUnion._unionTypes._byref = (IntPtr)Unsafe.AsPointer(ref value._typeUnion._unionTypes._byref); + break; + } + VariantType = (value.VariantType | VarEnum.VT_BYREF); + } + + private unsafe void SetAsByref(ref T value, VarEnum type) + { + Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise + VariantType = type | VarEnum.VT_BYREF; + _typeUnion._unionTypes._byref = (IntPtr)Unsafe.AsPointer(ref value); + } + + internal static System.Reflection.PropertyInfo GetAccessor(VarEnum varType) + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(Variant).GetProperty(nameof(AsI1)); + case VarEnum.VT_I2: return typeof(Variant).GetProperty(nameof(AsI2)); + case VarEnum.VT_I4: return typeof(Variant).GetProperty(nameof(AsI4)); + case VarEnum.VT_I8: return typeof(Variant).GetProperty(nameof(AsI8)); + case VarEnum.VT_UI1: return typeof(Variant).GetProperty(nameof(AsUi1)); + case VarEnum.VT_UI2: return typeof(Variant).GetProperty(nameof(AsUi2)); + case VarEnum.VT_UI4: return typeof(Variant).GetProperty(nameof(AsUi4)); + case VarEnum.VT_UI8: return typeof(Variant).GetProperty(nameof(AsUi8)); + case VarEnum.VT_INT: return typeof(Variant).GetProperty(nameof(AsInt)); + case VarEnum.VT_UINT: return typeof(Variant).GetProperty(nameof(AsUint)); + case VarEnum.VT_BOOL: return typeof(Variant).GetProperty(nameof(AsBool)); + case VarEnum.VT_ERROR: return typeof(Variant).GetProperty(nameof(AsError)); + case VarEnum.VT_R4: return typeof(Variant).GetProperty(nameof(AsR4)); + case VarEnum.VT_R8: return typeof(Variant).GetProperty(nameof(AsR8)); + case VarEnum.VT_DECIMAL: return typeof(Variant).GetProperty(nameof(AsDecimal)); + case VarEnum.VT_CY: return typeof(Variant).GetProperty(nameof(AsCy)); + case VarEnum.VT_DATE: return typeof(Variant).GetProperty(nameof(AsDate)); + case VarEnum.VT_BSTR: return typeof(Variant).GetProperty(nameof(AsBstr)); + case VarEnum.VT_UNKNOWN: return typeof(Variant).GetProperty(nameof(AsUnknown)); + case VarEnum.VT_DISPATCH: return typeof(Variant).GetProperty(nameof(AsDispatch)); + + case VarEnum.VT_VARIANT: + case VarEnum.VT_RECORD: + case VarEnum.VT_ARRAY: + return typeof(Variant).GetProperty(nameof(AsVariant)); + + default: + throw new NotSupportedException(); + } + } + + internal static System.Reflection.MethodInfo GetByrefSetter(VarEnum varType) + { + switch (varType) + { + case VarEnum.VT_I1: return typeof(Variant).GetMethod(nameof(SetAsByrefI1)); + case VarEnum.VT_I2: return typeof(Variant).GetMethod(nameof(SetAsByrefI2)); + case VarEnum.VT_I4: return typeof(Variant).GetMethod(nameof(SetAsByrefI4)); + case VarEnum.VT_I8: return typeof(Variant).GetMethod(nameof(SetAsByrefI8)); + case VarEnum.VT_UI1: return typeof(Variant).GetMethod(nameof(SetAsByrefUi1)); + case VarEnum.VT_UI2: return typeof(Variant).GetMethod(nameof(SetAsByrefUi2)); + case VarEnum.VT_UI4: return typeof(Variant).GetMethod(nameof(SetAsByrefUi4)); + case VarEnum.VT_UI8: return typeof(Variant).GetMethod(nameof(SetAsByrefUi8)); + case VarEnum.VT_INT: return typeof(Variant).GetMethod(nameof(SetAsByrefInt)); + case VarEnum.VT_UINT: return typeof(Variant).GetMethod(nameof(SetAsByrefUint)); + case VarEnum.VT_BOOL: return typeof(Variant).GetMethod(nameof(SetAsByrefBool)); + case VarEnum.VT_ERROR: return typeof(Variant).GetMethod(nameof(SetAsByrefError)); + case VarEnum.VT_R4: return typeof(Variant).GetMethod(nameof(SetAsByrefR4)); + case VarEnum.VT_R8: return typeof(Variant).GetMethod(nameof(SetAsByrefR8)); + case VarEnum.VT_DECIMAL: return typeof(Variant).GetMethod(nameof(SetAsByrefDecimal)); + case VarEnum.VT_CY: return typeof(Variant).GetMethod(nameof(SetAsByrefCy)); + case VarEnum.VT_DATE: return typeof(Variant).GetMethod(nameof(SetAsByrefDate)); + case VarEnum.VT_BSTR: return typeof(Variant).GetMethod(nameof(SetAsByrefBstr)); + case VarEnum.VT_UNKNOWN: return typeof(Variant).GetMethod(nameof(SetAsByrefUnknown)); + case VarEnum.VT_DISPATCH: return typeof(Variant).GetMethod(nameof(SetAsByrefDispatch)); + + case VarEnum.VT_VARIANT: + return typeof(Variant).GetMethod(nameof(SetAsByrefVariant)); + case VarEnum.VT_RECORD: + case VarEnum.VT_ARRAY: + return typeof(Variant).GetMethod(nameof(SetAsByrefVariantIndirect)); + + default: + throw new NotSupportedException(); + } + } + + public override string ToString() => $"Variant ({VariantType})"; + + public void SetAsIConvertible(IConvertible value) + { + Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise + + TypeCode tc = value.GetTypeCode(); + CultureInfo ci = CultureInfo.CurrentCulture; + + switch (tc) + { + case TypeCode.Empty: break; + case TypeCode.Object: AsUnknown = value; break; + case TypeCode.DBNull: SetAsNULL(); break; + case TypeCode.Boolean: AsBool = value.ToBoolean(ci); break; + case TypeCode.Char: AsUi2 = value.ToChar(ci); break; + case TypeCode.SByte: AsI1 = value.ToSByte(ci); break; + case TypeCode.Byte: AsUi1 = value.ToByte(ci); break; + case TypeCode.Int16: AsI2 = value.ToInt16(ci); break; + case TypeCode.UInt16: AsUi2 = value.ToUInt16(ci); break; + case TypeCode.Int32: AsI4 = value.ToInt32(ci); break; + case TypeCode.UInt32: AsUi4 = value.ToUInt32(ci); break; + case TypeCode.Int64: AsI8 = value.ToInt64(ci); break; + case TypeCode.UInt64: AsI8 = value.ToInt64(ci); break; + case TypeCode.Single: AsR4 = value.ToSingle(ci); break; + case TypeCode.Double: AsR8 = value.ToDouble(ci); break; + case TypeCode.Decimal: AsDecimal = value.ToDecimal(ci); break; + case TypeCode.DateTime: AsDate = value.ToDateTime(ci); break; + case TypeCode.String: AsBstr = value.ToString(ci); break; + + default: + throw new NotSupportedException(); + } + } + } +} diff --git a/src/System.Management.Automation/engine/ComInterop/Variant.cs b/src/System.Management.Automation/engine/ComInterop/Variant.cs deleted file mode 100644 index 0e1561fbe17..00000000000 --- a/src/System.Management.Automation/engine/ComInterop/Variant.cs +++ /dev/null @@ -1,1004 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#if !SILVERLIGHT // ComObject - -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Runtime.InteropServices; -//using Microsoft.Scripting.Utils; -//using Microsoft.Scripting.Generation; -using Assert = System.Management.Automation.Interpreter.Assert; - -namespace System.Management.Automation.ComInterop -{ - /// - /// Variant is the basic COM type for late-binding. It can contain any other COM data type. - /// This type definition precisely matches the unmanaged data layout so that the struct can be passed - /// to and from COM calls. - /// - [StructLayout(LayoutKind.Explicit)] - internal struct Variant - { -#if DEBUG - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2207:InitializeValueTypeStaticFieldsInline")] - static Variant() - { - // Variant size is the size of 4 pointers (16 bytes) on a 32-bit processor, - // and 3 pointers (24 bytes) on a 64-bit processor. - int intPtrSize = Marshal.SizeOf(typeof(IntPtr)); - int variantSize = Marshal.SizeOf(typeof(Variant)); - if (intPtrSize == 4) - { - Debug.Assert(variantSize == (4 * intPtrSize)); - } - else - { - Debug.Assert(intPtrSize == 8); - Debug.Assert(variantSize == (3 * intPtrSize)); - } - } -#endif - - // Most of the data types in the Variant are carried in _typeUnion - [FieldOffset(0)] - private TypeUnion _typeUnion; - - // Decimal is the largest data type and it needs to use the space that is normally unused in TypeUnion._wReserved1, etc. - // Hence, it is declared to completely overlap with TypeUnion. A Decimal does not use the first two bytes, and so - // TypeUnion._vt can still be used to encode the type. - [FieldOffset(0)] - private Decimal _decimal; - - [StructLayout(LayoutKind.Sequential)] - private struct TypeUnion - { - internal ushort _vt; - internal ushort _wReserved1; - internal ushort _wReserved2; - internal ushort _wReserved3; - - internal UnionTypes _unionTypes; - } - - [StructLayout(LayoutKind.Sequential)] - private struct Record - { - private IntPtr _record; - private IntPtr _recordInfo; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] - [StructLayout(LayoutKind.Explicit)] - private struct UnionTypes - { - #region Generated Outer Variant union types - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_UnionTypes from: generate_comdispatch.py - - [FieldOffset(0)] - internal sbyte _i1; - [FieldOffset(0)] - internal Int16 _i2; - [FieldOffset(0)] - internal Int32 _i4; - [FieldOffset(0)] - internal Int64 _i8; - [FieldOffset(0)] - internal byte _ui1; - [FieldOffset(0)] - internal UInt16 _ui2; - [FieldOffset(0)] - internal UInt32 _ui4; - [FieldOffset(0)] - internal UInt64 _ui8; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] - [FieldOffset(0)] - internal IntPtr _int; - [FieldOffset(0)] - internal UIntPtr _uint; - [FieldOffset(0)] - internal Int16 _bool; - [FieldOffset(0)] - internal Int32 _error; - [FieldOffset(0)] - internal Single _r4; - [FieldOffset(0)] - internal double _r8; - [FieldOffset(0)] - internal Int64 _cy; - [FieldOffset(0)] - internal double _date; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] - [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - [FieldOffset(0)] - internal IntPtr _bstr; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] - [FieldOffset(0)] - internal IntPtr _unknown; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] - [FieldOffset(0)] - internal IntPtr _dispatch; - - // *** END GENERATED CODE *** - - #endregion - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - [FieldOffset(0)] - internal IntPtr _byref; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] - [FieldOffset(0)] - internal Record _record; - } - - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "Variant ({0})", VariantType); - } - - /// - /// Primitive types are the basic COM types. It includes valuetypes like ints, but also reference types - /// like BStrs. It does not include composite types like arrays and user-defined COM types (IUnknown/IDispatch). - /// - internal static bool IsPrimitiveType(VarEnum varEnum) - { - switch (varEnum) - { - #region Generated Outer Variant IsPrimitiveType - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_IsPrimitiveType from: generate_comdispatch.py - - case VarEnum.VT_I1: - case VarEnum.VT_I2: - case VarEnum.VT_I4: - case VarEnum.VT_I8: - case VarEnum.VT_UI1: - case VarEnum.VT_UI2: - case VarEnum.VT_UI4: - case VarEnum.VT_UI8: - case VarEnum.VT_INT: - case VarEnum.VT_UINT: - case VarEnum.VT_BOOL: - case VarEnum.VT_ERROR: - case VarEnum.VT_R4: - case VarEnum.VT_R8: - case VarEnum.VT_DECIMAL: - case VarEnum.VT_CY: - case VarEnum.VT_DATE: - case VarEnum.VT_BSTR: - - // *** END GENERATED CODE *** - - #endregion - return true; - } - - return false; - } - - /// - /// Get the managed object representing the Variant. - /// - /// - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - public object ToObject() - { - // Check the simple case upfront - if (IsEmpty) - { - return null; - } - - switch (VariantType) - { - case VarEnum.VT_NULL: return DBNull.Value; - - #region Generated Outer Variant ToObject - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_ToObject from: generate_comdispatch.py - - case VarEnum.VT_I1: return AsI1; - case VarEnum.VT_I2: return AsI2; - case VarEnum.VT_I4: return AsI4; - case VarEnum.VT_I8: return AsI8; - case VarEnum.VT_UI1: return AsUi1; - case VarEnum.VT_UI2: return AsUi2; - case VarEnum.VT_UI4: return AsUi4; - case VarEnum.VT_UI8: return AsUi8; - case VarEnum.VT_INT: return AsInt; - case VarEnum.VT_UINT: return AsUint; - case VarEnum.VT_BOOL: return AsBool; - case VarEnum.VT_ERROR: return AsError; - case VarEnum.VT_R4: return AsR4; - case VarEnum.VT_R8: return AsR8; - case VarEnum.VT_DECIMAL: return AsDecimal; - case VarEnum.VT_CY: return AsCy; - case VarEnum.VT_DATE: return AsDate; - case VarEnum.VT_BSTR: return AsBstr; - case VarEnum.VT_UNKNOWN: return AsUnknown; - case VarEnum.VT_DISPATCH: return AsDispatch; - case VarEnum.VT_VARIANT: return AsVariant; - - // *** END GENERATED CODE *** - - #endregion - - default: - return AsVariant; - } - } - - /// - /// Release any unmanaged memory associated with the Variant. - /// - /// - public void Clear() - { - // We do not need to call OLE32's VariantClear for primitive types or ByRefs - // to safe ourselves the cost of interop transition. - // ByRef indicates the memory is not owned by the VARIANT itself while - // primitive types do not have any resources to free up. - // Hence, only safearrays, BSTRs, interfaces and user types are - // handled differently. - VarEnum vt = VariantType; - if ((vt & VarEnum.VT_BYREF) != 0) - { - VariantType = VarEnum.VT_EMPTY; - } - else if ( - ((vt & VarEnum.VT_ARRAY) != 0) || - ((vt) == VarEnum.VT_BSTR) || - ((vt) == VarEnum.VT_UNKNOWN) || - ((vt) == VarEnum.VT_DISPATCH) || - ((vt) == VarEnum.VT_RECORD) - ) - { - IntPtr variantPtr = UnsafeMethods.ConvertVariantByrefToPtr(ref this); - NativeMethods.VariantClear(variantPtr); - Debug.Assert(IsEmpty); - } - else - { - VariantType = VarEnum.VT_EMPTY; - } - } - - public VarEnum VariantType - { - get - { - return (VarEnum)_typeUnion._vt; - } - - set - { - _typeUnion._vt = (ushort)value; - } - } - - internal bool IsEmpty - { - get - { - return _typeUnion._vt == ((ushort)VarEnum.VT_EMPTY); - } - } - - public void SetAsNull() - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_NULL; - } - - public void SetAsIConvertible(IConvertible value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - - TypeCode tc = value.GetTypeCode(); - CultureInfo ci = CultureInfo.CurrentCulture; - - switch (tc) - { - case TypeCode.Empty: break; - case TypeCode.Object: AsUnknown = value; break; - case TypeCode.DBNull: SetAsNull(); break; - case TypeCode.Boolean: AsBool = value.ToBoolean(ci); break; - case TypeCode.Char: AsUi2 = value.ToChar(ci); break; - case TypeCode.SByte: AsI1 = value.ToSByte(ci); break; - case TypeCode.Byte: AsUi1 = value.ToByte(ci); break; - case TypeCode.Int16: AsI2 = value.ToInt16(ci); break; - case TypeCode.UInt16: AsUi2 = value.ToUInt16(ci); break; - case TypeCode.Int32: AsI4 = value.ToInt32(ci); break; - case TypeCode.UInt32: AsUi4 = value.ToUInt32(ci); break; - case TypeCode.Int64: AsI8 = value.ToInt64(ci); break; - case TypeCode.UInt64: AsI8 = value.ToInt64(ci); break; - case TypeCode.Single: AsR4 = value.ToSingle(ci); break; - case TypeCode.Double: AsR8 = value.ToDouble(ci); break; - case TypeCode.Decimal: AsDecimal = value.ToDecimal(ci); break; - case TypeCode.DateTime: AsDate = value.ToDateTime(ci); break; - case TypeCode.String: AsBstr = value.ToString(ci); break; - - default: - throw Assert.Unreachable; - } - } - - #region Generated Outer Variant accessors - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_accessors from: generate_comdispatch.py - - // VT_I1 - public sbyte AsI1 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_I1); - return _typeUnion._unionTypes._i1; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_I1; - _typeUnion._unionTypes._i1 = value; - } - } - - public void SetAsByrefI1(ref sbyte value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_I1 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertSByteByrefToPtr(ref value); - } - - // VT_I2 - public Int16 AsI2 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_I2); - return _typeUnion._unionTypes._i2; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_I2; - _typeUnion._unionTypes._i2 = value; - } - } - - public void SetAsByrefI2(ref Int16 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_I2 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt16ByrefToPtr(ref value); - } - - // VT_I4 - public Int32 AsI4 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_I4); - return _typeUnion._unionTypes._i4; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_I4; - _typeUnion._unionTypes._i4 = value; - } - } - - public void SetAsByrefI4(ref Int32 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_I4 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt32ByrefToPtr(ref value); - } - - // VT_I8 - public Int64 AsI8 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_I8); - return _typeUnion._unionTypes._i8; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_I8; - _typeUnion._unionTypes._i8 = value; - } - } - - public void SetAsByrefI8(ref Int64 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_I8 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt64ByrefToPtr(ref value); - } - - // VT_UI1 - public byte AsUi1 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UI1); - return _typeUnion._unionTypes._ui1; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UI1; - _typeUnion._unionTypes._ui1 = value; - } - } - - public void SetAsByrefUi1(ref byte value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UI1 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertByteByrefToPtr(ref value); - } - - // VT_UI2 - public UInt16 AsUi2 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UI2); - return _typeUnion._unionTypes._ui2; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UI2; - _typeUnion._unionTypes._ui2 = value; - } - } - - public void SetAsByrefUi2(ref UInt16 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UI2 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertUInt16ByrefToPtr(ref value); - } - - // VT_UI4 - public UInt32 AsUi4 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UI4); - return _typeUnion._unionTypes._ui4; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UI4; - _typeUnion._unionTypes._ui4 = value; - } - } - - public void SetAsByrefUi4(ref UInt32 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UI4 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertUInt32ByrefToPtr(ref value); - } - - // VT_UI8 - public UInt64 AsUi8 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UI8); - return _typeUnion._unionTypes._ui8; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UI8; - _typeUnion._unionTypes._ui8 = value; - } - } - - public void SetAsByrefUi8(ref UInt64 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UI8 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertUInt64ByrefToPtr(ref value); - } - - // VT_INT - public IntPtr AsInt - { - get - { - Debug.Assert(VariantType == VarEnum.VT_INT); - return _typeUnion._unionTypes._int; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_INT; - _typeUnion._unionTypes._int = value; - } - } - - public void SetAsByrefInt(ref IntPtr value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_INT | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertIntPtrByrefToPtr(ref value); - } - - // VT_UINT - public UIntPtr AsUint - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UINT); - return _typeUnion._unionTypes._uint; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UINT; - _typeUnion._unionTypes._uint = value; - } - } - - public void SetAsByrefUint(ref UIntPtr value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UINT | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertUIntPtrByrefToPtr(ref value); - } - - // VT_BOOL - public bool AsBool - { - get - { - Debug.Assert(VariantType == VarEnum.VT_BOOL); - return _typeUnion._unionTypes._bool != 0; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_BOOL; - _typeUnion._unionTypes._bool = value ? (Int16)(-1) : (Int16)0; - } - } - - public void SetAsByrefBool(ref Int16 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_BOOL | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt16ByrefToPtr(ref value); - } - - // VT_ERROR - public Int32 AsError - { - get - { - Debug.Assert(VariantType == VarEnum.VT_ERROR); - return _typeUnion._unionTypes._error; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_ERROR; - _typeUnion._unionTypes._error = value; - } - } - - public void SetAsByrefError(ref Int32 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_ERROR | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt32ByrefToPtr(ref value); - } - - // VT_R4 - public Single AsR4 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_R4); - return _typeUnion._unionTypes._r4; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_R4; - _typeUnion._unionTypes._r4 = value; - } - } - - public void SetAsByrefR4(ref Single value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_R4 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertSingleByrefToPtr(ref value); - } - - // VT_R8 - public double AsR8 - { - get - { - Debug.Assert(VariantType == VarEnum.VT_R8); - return _typeUnion._unionTypes._r8; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_R8; - _typeUnion._unionTypes._r8 = value; - } - } - - public void SetAsByrefR8(ref double value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_R8 | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertDoubleByrefToPtr(ref value); - } - - // VT_DECIMAL - public Decimal AsDecimal - { - get - { - Debug.Assert(VariantType == VarEnum.VT_DECIMAL); - // The first byte of Decimal is unused, but usually set to 0 - Variant v = this; - v._typeUnion._vt = 0; - return v._decimal; - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_DECIMAL; - _decimal = value; - // _vt overlaps with _decimal, and should be set after setting _decimal - _typeUnion._vt = (ushort)VarEnum.VT_DECIMAL; - } - } - - public void SetAsByrefDecimal(ref Decimal value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_DECIMAL | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertDecimalByrefToPtr(ref value); - } - - // VT_CY - public Decimal AsCy - { - get - { - Debug.Assert(VariantType == VarEnum.VT_CY); - return Decimal.FromOACurrency(_typeUnion._unionTypes._cy); - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_CY; - _typeUnion._unionTypes._cy = Decimal.ToOACurrency(value); - } - } - - public void SetAsByrefCy(ref Int64 value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_CY | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertInt64ByrefToPtr(ref value); - } - - // VT_DATE - public DateTime AsDate - { - get - { - Debug.Assert(VariantType == VarEnum.VT_DATE); - return DateTime.FromOADate(_typeUnion._unionTypes._date); - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_DATE; - _typeUnion._unionTypes._date = value.ToOADate(); - } - } - - public void SetAsByrefDate(ref double value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_DATE | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertDoubleByrefToPtr(ref value); - } - - // VT_BSTR - public string AsBstr - { - get - { - Debug.Assert(VariantType == VarEnum.VT_BSTR); - if (_typeUnion._unionTypes._bstr != IntPtr.Zero) - { - return Marshal.PtrToStringBSTR(_typeUnion._unionTypes._bstr); - } - else - { - return null; - } - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_BSTR; - if (value != null) - { - Marshal.GetNativeVariantForObject(value, UnsafeMethods.ConvertVariantByrefToPtr(ref this)); - } - } - } - - public void SetAsByrefBstr(ref IntPtr value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_BSTR | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertIntPtrByrefToPtr(ref value); - } - - // VT_UNKNOWN - public object AsUnknown - { - get - { - Debug.Assert(VariantType == VarEnum.VT_UNKNOWN); - if (_typeUnion._unionTypes._dispatch != IntPtr.Zero) - { - return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._unknown); - } - else - { - return null; - } - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_UNKNOWN; - if (value != null) - { - _typeUnion._unionTypes._unknown = Marshal.GetIUnknownForObject(value); - } - } - } - - public void SetAsByrefUnknown(ref IntPtr value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_UNKNOWN | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertIntPtrByrefToPtr(ref value); - } - - // VT_DISPATCH - public object AsDispatch - { - get - { - Debug.Assert(VariantType == VarEnum.VT_DISPATCH); - if (_typeUnion._unionTypes._dispatch != IntPtr.Zero) - { - return Marshal.GetObjectForIUnknown(_typeUnion._unionTypes._dispatch); - } - else - { - return null; - } - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = VarEnum.VT_DISPATCH; - if (value != null) - { - _typeUnion._unionTypes._unknown = Marshal.GetIDispatchForObject(value); - } - } - } - - public void SetAsByrefDispatch(ref IntPtr value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_DISPATCH | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertIntPtrByrefToPtr(ref value); - } - - // *** END GENERATED CODE *** - - #endregion - - // VT_VARIANT - - public object AsVariant - { - get - { - return Marshal.GetObjectForNativeVariant(UnsafeMethods.ConvertVariantByrefToPtr(ref this)); - } - - set - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - if (value != null) - { - UnsafeMethods.InitVariantForObject(value, ref this); - } - } - } - - public void SetAsByrefVariant(ref Variant value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - VariantType = (VarEnum.VT_VARIANT | VarEnum.VT_BYREF); - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertVariantByrefToPtr(ref value); - } - - // constructs a ByRef variant to pass contents of another variant ByRef. - public void SetAsByrefVariantIndirect(ref Variant value) - { - Debug.Assert(IsEmpty); // The setter can only be called once as VariantClear might be needed otherwise - Debug.Assert((value.VariantType & VarEnum.VT_BYREF) == 0, "double indirection"); - - switch (value.VariantType) - { - case VarEnum.VT_EMPTY: - case VarEnum.VT_NULL: - // these cannot combine with VT_BYREF. Should try passing as a variant reference - SetAsByrefVariant(ref value); - return; - case VarEnum.VT_RECORD: - // VT_RECORD's are weird in that regardless of is the VT_BYREF flag is set or not - // they have the same internal representation. - _typeUnion._unionTypes._record = value._typeUnion._unionTypes._record; - break; - case VarEnum.VT_DECIMAL: - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertDecimalByrefToPtr(ref value._decimal); - break; - default: - _typeUnion._unionTypes._byref = UnsafeMethods.ConvertIntPtrByrefToPtr(ref value._typeUnion._unionTypes._byref); - break; - } - - VariantType = (value.VariantType | VarEnum.VT_BYREF); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - internal static System.Reflection.PropertyInfo GetAccessor(VarEnum varType) - { - switch (varType) - { - #region Generated Outer Variant accessors PropertyInfos - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_accessor_propertyinfo from: generate_comdispatch.py - - case VarEnum.VT_I1: return typeof(Variant).GetProperty("AsI1"); - case VarEnum.VT_I2: return typeof(Variant).GetProperty("AsI2"); - case VarEnum.VT_I4: return typeof(Variant).GetProperty("AsI4"); - case VarEnum.VT_I8: return typeof(Variant).GetProperty("AsI8"); - case VarEnum.VT_UI1: return typeof(Variant).GetProperty("AsUi1"); - case VarEnum.VT_UI2: return typeof(Variant).GetProperty("AsUi2"); - case VarEnum.VT_UI4: return typeof(Variant).GetProperty("AsUi4"); - case VarEnum.VT_UI8: return typeof(Variant).GetProperty("AsUi8"); - case VarEnum.VT_INT: return typeof(Variant).GetProperty("AsInt"); - case VarEnum.VT_UINT: return typeof(Variant).GetProperty("AsUint"); - case VarEnum.VT_BOOL: return typeof(Variant).GetProperty("AsBool"); - case VarEnum.VT_ERROR: return typeof(Variant).GetProperty("AsError"); - case VarEnum.VT_R4: return typeof(Variant).GetProperty("AsR4"); - case VarEnum.VT_R8: return typeof(Variant).GetProperty("AsR8"); - case VarEnum.VT_DECIMAL: return typeof(Variant).GetProperty("AsDecimal"); - case VarEnum.VT_CY: return typeof(Variant).GetProperty("AsCy"); - case VarEnum.VT_DATE: return typeof(Variant).GetProperty("AsDate"); - case VarEnum.VT_BSTR: return typeof(Variant).GetProperty("AsBstr"); - case VarEnum.VT_UNKNOWN: return typeof(Variant).GetProperty("AsUnknown"); - case VarEnum.VT_DISPATCH: return typeof(Variant).GetProperty("AsDispatch"); - - // *** END GENERATED CODE *** - - #endregion - - case VarEnum.VT_VARIANT: - case VarEnum.VT_RECORD: - case VarEnum.VT_ARRAY: - return typeof(Variant).GetProperty("AsVariant"); - - default: - throw Error.VariantGetAccessorNYI(varType); - } - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - internal static System.Reflection.MethodInfo GetByrefSetter(VarEnum varType) - { - switch (varType) - { - #region Generated Outer Variant byref setter - - // *** BEGIN GENERATED CODE *** - // generated by function: gen_byref_setters from: generate_comdispatch.py - - case VarEnum.VT_I1: return typeof(Variant).GetMethod("SetAsByrefI1"); - case VarEnum.VT_I2: return typeof(Variant).GetMethod("SetAsByrefI2"); - case VarEnum.VT_I4: return typeof(Variant).GetMethod("SetAsByrefI4"); - case VarEnum.VT_I8: return typeof(Variant).GetMethod("SetAsByrefI8"); - case VarEnum.VT_UI1: return typeof(Variant).GetMethod("SetAsByrefUi1"); - case VarEnum.VT_UI2: return typeof(Variant).GetMethod("SetAsByrefUi2"); - case VarEnum.VT_UI4: return typeof(Variant).GetMethod("SetAsByrefUi4"); - case VarEnum.VT_UI8: return typeof(Variant).GetMethod("SetAsByrefUi8"); - case VarEnum.VT_INT: return typeof(Variant).GetMethod("SetAsByrefInt"); - case VarEnum.VT_UINT: return typeof(Variant).GetMethod("SetAsByrefUint"); - case VarEnum.VT_BOOL: return typeof(Variant).GetMethod("SetAsByrefBool"); - case VarEnum.VT_ERROR: return typeof(Variant).GetMethod("SetAsByrefError"); - case VarEnum.VT_R4: return typeof(Variant).GetMethod("SetAsByrefR4"); - case VarEnum.VT_R8: return typeof(Variant).GetMethod("SetAsByrefR8"); - case VarEnum.VT_DECIMAL: return typeof(Variant).GetMethod("SetAsByrefDecimal"); - case VarEnum.VT_CY: return typeof(Variant).GetMethod("SetAsByrefCy"); - case VarEnum.VT_DATE: return typeof(Variant).GetMethod("SetAsByrefDate"); - case VarEnum.VT_BSTR: return typeof(Variant).GetMethod("SetAsByrefBstr"); - case VarEnum.VT_UNKNOWN: return typeof(Variant).GetMethod("SetAsByrefUnknown"); - case VarEnum.VT_DISPATCH: return typeof(Variant).GetMethod("SetAsByrefDispatch"); - - // *** END GENERATED CODE *** - - #endregion - - case VarEnum.VT_VARIANT: - return typeof(Variant).GetMethod("SetAsByrefVariant"); - case VarEnum.VT_RECORD: - case VarEnum.VT_ARRAY: - return typeof(Variant).GetMethod("SetAsByrefVariantIndirect"); - - default: - throw Error.VariantGetAccessorNYI(varType); - } - } - } -} - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/VariantArgBuilder.cs b/src/System.Management.Automation/engine/ComInterop/VariantArgBuilder.cs index b8a2b8c5e27..5ebfd3a11c4 100644 --- a/src/System.Management.Automation/engine/ComInterop/VariantArgBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/VariantArgBuilder.cs @@ -1,14 +1,10 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 +using System; using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif -using System.Runtime.InteropServices; using System.Reflection; +using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop { @@ -29,9 +25,9 @@ internal override Expression Marshal(Expression parameter) { parameter = Expression.Property( Helpers.Convert(parameter, typeof(VariantWrapper)), - typeof(VariantWrapper).GetProperty("WrappedObject") + typeof(VariantWrapper).GetProperty(nameof(VariantWrapper.WrappedObject)) ); - }; + } return Helpers.Convert(parameter, typeof(object)); } @@ -42,7 +38,7 @@ internal override Expression MarshalToRef(Expression parameter) // parameter == UnsafeMethods.GetVariantForObject(parameter); return Expression.Call( - typeof(UnsafeMethods).GetMethod("GetVariantForObject", BindingFlags.Static | System.Reflection.BindingFlags.NonPublic), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.GetVariantForObject), BindingFlags.Static | BindingFlags.NonPublic), parameter ); } @@ -52,7 +48,7 @@ internal override Expression UnmarshalFromRef(Expression value) // value == IntPtr.Zero ? null : Marshal.GetObjectForNativeVariant(value); Expression unmarshal = Expression.Call( - typeof(UnsafeMethods).GetMethod("GetObjectForVariant"), + typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.GetObjectForVariant)), value ); @@ -62,12 +58,9 @@ internal override Expression UnmarshalFromRef(Expression value) typeof(VariantWrapper).GetConstructor(new Type[] { typeof(object) }), unmarshal ); - }; + } return base.UnmarshalFromRef(unmarshal); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/VariantArray.cs b/src/System.Management.Automation/engine/ComInterop/VariantArray.cs index 0277e03f191..d4f8af57b74 100644 --- a/src/System.Management.Automation/engine/ComInterop/VariantArray.cs +++ b/src/System.Management.Automation/engine/ComInterop/VariantArray.cs @@ -1,17 +1,14 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; +using System.Management.Automation.InteropServices; using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -61,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) @@ -77,7 +89,7 @@ internal static Type GetStructType(int args) // See if we can find an existing type foreach (Type t in s_generatedTypes) { - int arity = int.Parse(t.Name.AsSpan("VariantArray".Length), NumberStyles.Integer, CultureInfo.InvariantCulture); + int arity = int.Parse(t.Name.AsSpan("VariantArray".Length), NumberStyles.Integer, CultureInfo.InvariantCulture); if (size == arity) { return t; @@ -93,18 +105,14 @@ internal static Type GetStructType(int args) private static Type CreateCustomType(int size) { - var attrs = TypeAttributes.NotPublic | TypeAttributes.SequentialLayout; + TypeAttributes attrs = TypeAttributes.NotPublic | TypeAttributes.SequentialLayout; TypeBuilder type = UnsafeMethods.DynamicModule.DefineType("VariantArray" + size, attrs, typeof(ValueType)); - var T = type.DefineGenericParameters(new string[] { "T" })[0]; + GenericTypeParameterBuilder T = type.DefineGenericParameters(new string[] { "T" })[0]; for (int i = 0; i < size; i++) { type.DefineField("Element" + i, T, FieldAttributes.Public); } - return type.CreateType(); } } } - -#endif - diff --git a/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs b/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs index 06d449ece27..baabb25cd75 100644 --- a/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs +++ b/src/System.Management.Automation/engine/ComInterop/VariantBuilder.cs @@ -1,14 +1,9 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. -#if !SILVERLIGHT // ComObject - -#if !CLR2 -using System.Linq.Expressions; -#else -using Microsoft.Scripting.Ast; -#endif using System.Diagnostics; +using System.Linq.Expressions; +using System.Management.Automation.InteropServices; using System.Runtime.InteropServices; namespace System.Management.Automation.ComInterop @@ -21,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) @@ -36,9 +32,9 @@ internal bool IsByRef internal Expression InitializeArgumentVariant(MemberExpression variant, Expression parameter) { - // NOTE: we must remember our variant - // the reason is that argument order does not map exactly to the order of variants for invoke - // and when we are doing clean-up we must be sure we are cleaning the variant we have initialized. + //NOTE: we must remember our variant + //the reason is that argument order does not map exactly to the order of variants for invoke + //and when we are doing clean-up we must be sure we are cleaning the variant we have initialized. _variant = variant; @@ -47,7 +43,7 @@ internal Expression InitializeArgumentVariant(MemberExpression variant, Expressi // temp = argument // paramVariants._elementN.SetAsByrefT(ref temp) Debug.Assert(TempVariable == null); - var argExpr = _argBuilder.MarshalToRef(parameter); + Expression argExpr = _argBuilder.MarshalToRef(parameter); TempVariable = Expression.Variable(argExpr.Type, null); return Expression.Block( @@ -68,7 +64,7 @@ internal Expression InitializeArgumentVariant(MemberExpression variant, Expressi { return Expression.Call( variant, - typeof(Variant).GetMethod("SetAsIConvertible"), + typeof(Variant).GetMethod(nameof(Variant.SetAsIConvertible)), argument ); } @@ -97,7 +93,7 @@ internal Expression InitializeArgumentVariant(MemberExpression variant, Expressi case VarEnum.VT_NULL: // paramVariants._elementN.SetAsNull(); - return Expression.Call(variant, typeof(Variant).GetMethod("SetAsNull")); + return Expression.Call(variant, typeof(Variant).GetMethod(nameof(Variant.SetAsNULL))); default: Debug.Assert(false, "Unexpected VarEnum"); @@ -107,7 +103,7 @@ internal Expression InitializeArgumentVariant(MemberExpression variant, Expressi private static Expression Release(Expression pUnk) { - return Expression.Call(typeof(UnsafeMethods).GetMethod("IUnknownReleaseNotZero"), pUnk); + return Expression.Call(typeof(UnsafeMethods).GetMethod(nameof(UnsafeMethods.IUnknownReleaseNotZero)), pUnk); } internal Expression Clear() @@ -117,24 +113,26 @@ internal Expression Clear() if (_argBuilder is StringArgBuilder) { Debug.Assert(TempVariable != null); - return Expression.Call(typeof(Marshal).GetMethod("FreeBSTR"), TempVariable); + return Expression.Call(typeof(Marshal).GetMethod(nameof(Marshal.FreeBSTR)), TempVariable); } - else if (_argBuilder is DispatchArgBuilder) + + if (_argBuilder is DispatchArgBuilder) { Debug.Assert(TempVariable != null); return Release(TempVariable); } - else if (_argBuilder is UnknownArgBuilder) + + if (_argBuilder is UnknownArgBuilder) { Debug.Assert(TempVariable != null); return Release(TempVariable); } - else if (_argBuilder is VariantArgBuilder) + + if (_argBuilder is VariantArgBuilder) { Debug.Assert(TempVariable != null); - return Expression.Call(TempVariable, typeof(Variant).GetMethod("Clear")); + return Expression.Call(TempVariable, typeof(Variant).GetMethod(nameof(Variant.Clear))); } - return null; } @@ -151,7 +149,7 @@ internal Expression Clear() case VarEnum.VT_RECORD: case VarEnum.VT_VARIANT: // paramVariants._elementN.Clear() - return Expression.Call(_variant, typeof(Variant).GetMethod("Clear")); + return Expression.Call(_variant, typeof(Variant).GetMethod(nameof(Variant.Clear))); default: Debug.Assert(Variant.IsPrimitiveType(_targetComType), "Unexpected VarEnum"); @@ -165,7 +163,6 @@ internal Expression UpdateFromReturn(Expression parameter) { return null; } - return Expression.Assign( parameter, Helpers.Convert( @@ -176,6 +173,3 @@ internal Expression UpdateFromReturn(Expression parameter) } } } - -#endif - diff --git a/src/System.Management.Automation/engine/CommandBase.cs b/src/System.Management.Automation/engine/CommandBase.cs index 720a93ab85d..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 { @@ -76,7 +75,7 @@ internal InternalCommand() /// The invocation object for this command. internal InvocationInfo MyInvocation { - get { return _myInvocation ?? (_myInvocation = new InvocationInfo(this)); } + get { return _myInvocation ??= new InvocationInfo(this); } } /// @@ -89,7 +88,10 @@ internal InvocationInfo MyInvocation /// internal PSObject CurrentPipelineObject { - get { return currentObjectInPipeline; } + get + { + return currentObjectInPipeline; + } set { @@ -129,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. /// @@ -155,7 +164,10 @@ internal CommandInfo CommandInfo /// internal ExecutionContext Context { - get { return _context; } + get + { + return _context; + } set { @@ -227,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 /// @@ -266,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. @@ -280,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 @@ -361,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. /// @@ -470,7 +512,7 @@ public ProviderIntrinsics InvokeProvider { using (PSTransactionManager.GetEngineProtectionScope()) { - return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); + return _invokeProvider ??= new ProviderIntrinsics(this); } } } @@ -543,7 +585,6 @@ public object GetVariableValue(string name) } /// - public object GetVariableValue(string name, object defaultValue) { using (PSTransactionManager.GetEngineProtectionScope()) @@ -561,4 +602,3 @@ public object GetVariableValue(string name, object defaultValue) #endregion public_methods } } - diff --git a/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/CommandCompletion.cs index e00e0d737fc..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; } @@ -157,7 +159,7 @@ public static CommandCompletion CompleteInput(string input, int cursorIndex, Has } // If we are in a debugger stop, let the debugger do the command completion. - var debugger = (powershell.Runspace != null) ? powershell.Runspace.Debugger : null; + var debugger = powershell.Runspace?.Debugger; if ((debugger != null) && debugger.InBreakpoint) { return CompleteInputInDebugger(input, cursorIndex, options, debugger); @@ -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,8 +226,13 @@ 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 != null) ? powershell.Runspace.Debugger : null; + var debugger = powershell.Runspace?.Debugger; if ((debugger != null) && debugger.InBreakpoint) { return CompleteInputInDebugger(ast, tokens, cursorPosition, options, debugger); @@ -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,807 +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; - } - } - - 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) - { - 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); + int replacementIndex = -1; + int replacementLength = -1; + List results = null; - 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) + // 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) { - 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 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); + scopeToRestore = null; } - } - - 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; - } - - CommandAndName nextCommandAndName = cmdlets[lookAhead]; - - if (string.Equals( - commandAndName.CommandName.ShortName, - nextCommandAndName.CommandName.ShortName, - StringComparison.OrdinalIgnoreCase)) + try { - AddCommandResult(commandAndName, true, completingAtStartOfLine, quote, results); - previousMatched = true; - } - else - { - 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 = 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; - } + var completionResults = results ?? EmptyCompletionResult; - // 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(); - - // 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); + return new CommandCompletion( + new Collection(completionResults), + -1, + replacementIndex, + replacementLength); } - - 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 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(delegate (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; - } - - 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 bbc85aa4003..9c4a61a6833 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -8,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 { @@ -19,18 +22,29 @@ internal class CompletionContext // This is how we can tell if we're trying to complete part of something (like a member) // or complete an argument, where TokenBeforeCursor could be a parameter name. internal Token TokenAtCursor { get; set; } + internal Token TokenBeforeCursor { get; set; } + internal IScriptPosition CursorPosition { get; set; } internal PowerShellExecutionHelper Helper { get; set; } + internal Hashtable Options { get; set; } + internal Dictionary CustomArgumentCompleters { get; set; } + internal Dictionary NativeArgumentCompleters { get; set; } + internal string WordToComplete { get; set; } + internal int ReplacementIndex { get; set; } + internal int ReplacementLength { get; set; } + internal ExecutionContext ExecutionContext { get; set; } + internal PseudoBindingInfo PseudoBindingInfo { get; set; } + internal TypeInferenceContext TypeInferenceContext { get; set; } internal bool GetOption(string option, bool @default) @@ -94,26 +108,32 @@ private static bool IsCursorOutsideOfExtent(IScriptPosition cursor, IScriptExten return cursor.Offset < extent.StartOffset || cursor.Offset > extent.EndOffset; } - internal CompletionContext CreateCompletionContext(PowerShell powerShell) + internal readonly struct AstAnalysisContext { - var typeInferenceContext = new TypeInferenceContext(powerShell); - return InitializeCompletionContext(typeInferenceContext); - } + internal AstAnalysisContext(Token tokenAtCursor, Token tokenBeforeCursor, List relatedAsts, int replacementIndex) + { + TokenAtCursor = tokenAtCursor; + TokenBeforeCursor = tokenBeforeCursor; + RelatedAsts = relatedAsts; + ReplacementIndex = replacementIndex; + } - internal CompletionContext CreateCompletionContext(TypeInferenceContext typeInferenceContext) - { - return InitializeCompletionContext(typeInferenceContext); + internal readonly Token TokenAtCursor; + internal readonly Token TokenBeforeCursor; + internal readonly List RelatedAsts; + internal readonly int ReplacementIndex; } - private CompletionContext InitializeCompletionContext(TypeInferenceContext typeInferenceContext) + internal static AstAnalysisContext ExtractAstContext(Ast inputAst, Token[] inputTokens, IScriptPosition cursor) { + bool adjustLineAndColumn = false; + IScriptPosition positionForAstSearch = cursor; + Token tokenBeforeCursor = null; - IScriptPosition positionForAstSearch = _cursorPosition; - var adjustLineAndColumn = false; - var tokenAtCursor = InterstingTokenAtCursorOrDefault(_tokens, _cursorPosition); + Token tokenAtCursor = InterstingTokenAtCursorOrDefault(inputTokens, cursor); if (tokenAtCursor == null) { - tokenBeforeCursor = InterstingTokenBeforeCursorOrDefault(_tokens, _cursorPosition); + tokenBeforeCursor = InterstingTokenBeforeCursorOrDefault(inputTokens, cursor); if (tokenBeforeCursor != null) { positionForAstSearch = tokenBeforeCursor.Extent.EndScriptPosition; @@ -125,30 +145,62 @@ private CompletionContext InitializeCompletionContext(TypeInferenceContext typeI var stringExpandableToken = tokenAtCursor as StringExpandableToken; if (stringExpandableToken?.NestedTokens != null) { - tokenAtCursor = InterstingTokenAtCursorOrDefault(stringExpandableToken.NestedTokens, _cursorPosition) ?? stringExpandableToken; + tokenAtCursor = InterstingTokenAtCursorOrDefault(stringExpandableToken.NestedTokens, cursor) ?? stringExpandableToken; } } - var asts = AstSearcher.FindAll(_ast, ast => IsCursorWithinOrJustAfterExtent(positionForAstSearch, ast.Extent), searchNestedScriptBlocks: true).ToList(); + int replacementIndex = adjustLineAndColumn ? cursor.Offset : 0; + List relatedAsts = AstSearcher.FindAll( + inputAst, + ast => IsCursorWithinOrJustAfterExtent(positionForAstSearch, ast.Extent), + searchNestedScriptBlocks: true).ToList(); - Diagnostics.Assert(tokenAtCursor == null || tokenBeforeCursor == null, "Only one of these tokens can be non-null"); + if (relatedAsts.Count == 0) + { + relatedAsts.Add(inputAst); + } - if (typeInferenceContext.CurrentTypeDefinitionAst == null) + // 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) { - typeInferenceContext.CurrentTypeDefinitionAst = Ast.GetAncestorTypeDefinitionAst(asts.Last()); + 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); + } + + internal CompletionContext CreateCompletionContext(PowerShell powerShell) + { + var typeInferenceContext = new TypeInferenceContext(powerShell); + return InitializeCompletionContext(typeInferenceContext); + } + + internal CompletionContext CreateCompletionContext(TypeInferenceContext typeInferenceContext) + { + return InitializeCompletionContext(typeInferenceContext); + } + + private CompletionContext InitializeCompletionContext(TypeInferenceContext typeInferenceContext) + { + var astContext = ExtractAstContext(_ast, _tokens, _cursorPosition); + + typeInferenceContext.CurrentTypeDefinitionAst ??= Ast.GetAncestorTypeDefinitionAst(astContext.RelatedAsts.Last()); + ExecutionContext executionContext = typeInferenceContext.ExecutionContext; return new CompletionContext { - TokenAtCursor = tokenAtCursor, - TokenBeforeCursor = tokenBeforeCursor, - CursorPosition = _cursorPosition, - RelatedAsts = asts, Options = _options, + CursorPosition = _cursorPosition, + TokenAtCursor = astContext.TokenAtCursor, + TokenBeforeCursor = astContext.TokenBeforeCursor, + RelatedAsts = astContext.RelatedAsts, + ReplacementIndex = astContext.ReplacementIndex, ExecutionContext = executionContext, - ReplacementIndex = adjustLineAndColumn ? _cursorPosition.Offset : 0, TypeInferenceContext = typeInferenceContext, Helper = typeInferenceContext.Helper, CustomArgumentCompleters = executionContext.CustomArgumentCompleters, @@ -156,14 +208,32 @@ private CompletionContext InitializeCompletionContext(TypeInferenceContext typeI }; } - private static Token InterstingTokenAtCursorOrDefault(IEnumerable tokens, IScriptPosition cursorPosition) + private static Token InterstingTokenAtCursorOrDefault(IReadOnlyList tokens, IScriptPosition cursorPosition) { - return tokens.LastOrDefault(token => IsCursorWithinOrJustAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token)); + for (int i = tokens.Count - 1; i >= 0; --i) + { + Token token = tokens[i]; + if (IsCursorWithinOrJustAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token)) + { + return token; + } + } + + return null; } - private static Token InterstingTokenBeforeCursorOrDefault(IEnumerable tokens, IScriptPosition cursorPosition) + private static Token InterstingTokenBeforeCursorOrDefault(IReadOnlyList tokens, IScriptPosition cursorPosition) { - return tokens.LastOrDefault(token => IsCursorAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token)); + for (int i = tokens.Count - 1; i >= 0; --i) + { + Token token = tokens[i]; + if (IsCursorAfterExtent(cursorPosition, token.Extent) && IsInterestingToken(token)) + { + return token; + } + } + + return null; } private static Ast GetLastAstAtCursor(ScriptBlockAst scriptBlockAst, IScriptPosition cursorPosition) @@ -181,8 +251,7 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs { Tuple fileConditionTuple; - var errorStatement = lastAst as ErrorStatementAst; - if (errorStatement != null && errorStatement.Flags != null && errorStatement.Kind != null && tokenBeforeCursor != null && + if (lastAst is ErrorStatementAst errorStatement && errorStatement.Flags is not null && errorStatement.Kind is not null && tokenBeforeCursor is not null && errorStatement.Kind.Kind.Equals(TokenKind.Switch) && errorStatement.Flags.TryGetValue("file", out fileConditionTuple)) { // Handle "switch -file " @@ -192,25 +261,239 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs if (lastAst.Parent is CommandExpressionAst) { // Handle "switch -file m" or "switch -file *.ps1" - var pipeline = lastAst.Parent.Parent as PipelineAst; - if (pipeline == null) + if (lastAst.Parent.Parent is not PipelineAst pipeline) { return false; } - errorStatement = pipeline.Parent as ErrorStatementAst; - if (errorStatement == null || errorStatement.Kind == null || errorStatement.Flags == null) + if (pipeline.Parent is not ErrorStatementAst parentErrorStatement || parentErrorStatement.Kind is null || parentErrorStatement.Flags is null) { return false; } - return (errorStatement.Kind.Kind.Equals(TokenKind.Switch) && - errorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); + return (parentErrorStatement.Kind.Kind.Equals(TokenKind.Switch) && + parentErrorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); } return false; } + /// + /// Check if we should complete parameter names for switch cases on $PSBoundParameters.Keys + /// + private static List CompleteAgainstSwitchCaseCondition(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + PipelineAst conditionPipeline = null; + Ast switchAst = null; + + // Check if we're in a switch statement (complete) or error statement (incomplete switch) + if (lastAst.Parent is SwitchStatementAst switchStatementAst) + { + // Verify that the lastAst is one of the clause conditions (not in the body) + bool isClauseCondition = switchStatementAst.Clauses.Any(clause => clause.Item1 == lastAst); + + if (!isClauseCondition) + { + return null; + } + + conditionPipeline = switchStatementAst.Condition as PipelineAst; + switchAst = switchStatementAst; + } + else + { + // Check for incomplete switch parsed as ErrorStatementAst + if (lastAst.Parent is not ErrorStatementAst errorStatementAst || errorStatementAst.Kind is null || + errorStatementAst.Kind.Kind != TokenKind.Switch) + { + return null; + } + + // For ErrorStatementAst, the case value is in Bodies, condition is in Conditions + bool isInBodies = errorStatementAst.Bodies != null && errorStatementAst.Bodies.Any(body => body == lastAst); + + if (!isInBodies) + { + return null; + } + + // Get the condition from ErrorStatementAst.Conditions + if (errorStatementAst.Conditions != null && errorStatementAst.Conditions.Count > 0) + { + conditionPipeline = errorStatementAst.Conditions[0] as PipelineAst; + } + switchAst = errorStatementAst; + } + + if (conditionPipeline == null || conditionPipeline.PipelineElements.Count != 1) + { + return null; + } + + if (conditionPipeline.PipelineElements[0] is not CommandExpressionAst commandExpressionAst) + { + return null; + } + + // Check if the expression is a member access on $PSBoundParameters.Keys + if (commandExpressionAst.Expression is not MemberExpressionAst memberExpressionAst) + { + return null; + } + + // Check if the target is $PSBoundParameters + if (memberExpressionAst.Expression is not VariableExpressionAst variableExpressionAst || + !variableExpressionAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Check if the member is "Keys" + if (memberExpressionAst.Member is not StringConstantExpressionAst memberNameAst || + !memberNameAst.Value.Equals("Keys", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block by traversing up the AST + var paramBlockAst = FindNearestParamBlock(switchAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + return CreateParameterCompletionResults(paramBlockAst, wordToComplete); + } + + /// + /// Check if we should complete parameter names for $PSBoundParameters access patterns + /// Supports: $PSBoundParameters.ContainsKey('...'), $PSBoundParameters['...'], $PSBoundParameters.Remove('...') + /// + private static List CompleteAgainstPSBoundParametersAccess(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + // Must be a string constant + if (lastAst is not StringConstantExpressionAst stringAst) + { + return null; + } + + ExpressionAst targetAst = null; + + // Check for method invocation: $PSBoundParameters.ContainsKey('...') or $PSBoundParameters.Remove('...') + if (lastAst.Parent is InvokeMemberExpressionAst invokeMemberAst) + { + if (invokeMemberAst.Member is StringConstantExpressionAst memberName && + (memberName.Value.Equals("ContainsKey", StringComparison.OrdinalIgnoreCase) || + memberName.Value.Equals("Remove", StringComparison.OrdinalIgnoreCase))) + { + targetAst = invokeMemberAst.Expression; + } + } + // Check for indexer: $PSBoundParameters['...'] + else if (lastAst.Parent is IndexExpressionAst indexAst) + { + targetAst = indexAst.Target; + } + + if (targetAst is null) + { + return null; + } + + // Check if target is $PSBoundParameters + if (targetAst is not VariableExpressionAst variableAst || + !variableAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block + var paramBlockAst = FindNearestParamBlock(lastAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + + // Determine quote style based on the string constant type + string quoteChar = string.Empty; + if (stringAst.StringConstantType == StringConstantType.SingleQuoted) + { + quoteChar = "'"; + } + else if (stringAst.StringConstantType == StringConstantType.DoubleQuoted) + { + quoteChar = "\""; + } + + return CreateParameterCompletionResults(paramBlockAst, wordToComplete, quoteChar); + } + + /// + /// Finds the nearest ParamBlockAst by traversing up the AST hierarchy. + /// + /// The AST node to start searching from. + /// The nearest ParamBlockAst if found; otherwise, null. + private static ParamBlockAst FindNearestParamBlock(Ast startAst) + { + Ast current = startAst; + while (current != null) + { + if (current is FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst.Body?.ParamBlock; + } + else if (current is ScriptBlockAst scriptBlockAst) + { + var paramBlock = scriptBlockAst.ParamBlock; + if (paramBlock != null) + { + return paramBlock; + } + } + + current = current.Parent; + } + + return null; + } + + /// + /// Creates completion results from parameter names with optional quote wrapping. + /// + /// The parameter block containing parameters to complete. + /// The partial word to match against parameter names. + /// Optional quote character to wrap completion text (empty string for no quotes). + /// A list of completion results, or null if no matches found. + private static List CreateParameterCompletionResults( + ParamBlockAst paramBlockAst, + string wordToComplete, + string quoteChar = "") + { + var result = paramBlockAst.Parameters + .Select(parameter => parameter.Name.VariablePath.UserPath) + .Where(parameterName => parameterName.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + .Select(parameterName => + new CompletionResult( + quoteChar + parameterName + quoteChar, + parameterName, + CompletionResultType.ParameterValue, + parameterName)) + .ToList(); + + return result.Count > 0 ? result : null; + } + private static bool CompleteOperator(Token tokenAtCursor, Ast lastAst) { if (tokenAtCursor.Kind == TokenKind.Minus) @@ -318,7 +601,10 @@ internal List GetResults(PowerShell powerShell, out int replac completionContext.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - return GetResultHelper(completionContext, out replacementIndex, out replacementLength, false); + List results = GetResultHelper(completionContext, out replacementIndex, out replacementLength); + CompletionCompleters.RemoveLastNullCompletionResult(results); + + return results; } finally { @@ -329,7 +615,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; @@ -357,14 +643,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) @@ -407,16 +699,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: @@ -431,8 +720,30 @@ 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; + // Check for $PSBoundParameters indexer first + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + + 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: @@ -460,15 +771,26 @@ 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; result = CompletionCompleters.CompleteCommandArgument(completionContext); } + else if (lastAst is AttributeAst) + { + completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length; + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + } else { // @@ -481,8 +803,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; @@ -580,7 +901,7 @@ internal List GetResultHelper(CompletionContext completionCont completionContext.ReplacementLength = replacementLength = 0; result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); } - else if (lastAst is HashtableAst hashTableAst && !(lastAst.Parent is DynamicKeywordStatementAst) && CheckForPendingAssignment(hashTableAst)) + else if (lastAst is HashtableAst hashTableAst && lastAst.Parent is not DynamicKeywordStatementAst && CheckForPendingAssignment(hashTableAst)) { // Handle scenarios such as 'gci | Format-Table @{Label=' if incomplete parsing of the assignment. return null; @@ -596,6 +917,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 =' @@ -610,13 +944,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; @@ -665,8 +1068,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) || @@ -705,7 +1107,7 @@ internal List GetResultHelper(CompletionContext completionCont replacementLength = completionContext.ReplacementLength; } } - else if (!isQuotedString) + else { // // Handle completion of empty line within configuration statement @@ -740,6 +1142,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) { @@ -763,6 +1234,7 @@ internal List GetResultHelper(CompletionContext completionCont case TokenKind.Equals: case TokenKind.Comma: case TokenKind.AtParen: + case TokenKind.LParen: { if (lastAst is AssignmentStatementAst assignmentAst) { @@ -773,24 +1245,87 @@ internal List GetResultHelper(CompletionContext completionCont } } - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out unused); + if (lastAst is AttributeAst) + { + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + + if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + + break; + } + + result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _); break; } - case TokenKind.LParen: - if (lastAst is AttributeAst) + + case TokenKind.Break: + case TokenKind.Continue: { - completionContext.ReplacementLength = replacementLength = 0; - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + if ((lastAst is BreakStatementAst breakStatement && breakStatement.Label is null) + || (lastAst is ContinueStatementAst continueStatement && continueStatement.Label is null)) + { + result = CompleteLoopLabel(completionContext); + } + break; } - else + + 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) { - bool unused; - result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, - ref replacementIndex, ref replacementLength, out unused); + 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; } @@ -845,6 +1380,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. @@ -852,13 +1392,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) @@ -879,68 +1425,65 @@ internal List GetResultHelper(CompletionContext completionCont } // Helper method to auto complete hashtable key - private List GetResultForHashtable(CompletionContext completionContext) + 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) - { - // 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; - } - } + var tokenBeforeOrAtCursor = completionContext.TokenBeforeCursor ?? completionContext.TokenAtCursor; + if (tokenBeforeOrAtCursor.Kind != TokenKind.Semi) + { + 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; } // Helper method to look for an incomplete assignment pair in hash table. - private bool CheckForPendingAssignment(HashtableAst hashTableAst) + private static bool CheckForPendingAssignment(HashtableAst hashTableAst) { foreach (var keyValue in hashTableAst.KeyValuePairs) { @@ -997,7 +1540,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); @@ -1008,7 +1551,7 @@ private static string GetFirstLineSubString(string stringToComplete, out bool ha return stringToComplete; } - private Tuple GetHashEntryContainsCursor( + private static Tuple GetHashEntryContainsCursor( IScriptPosition cursor, HashtableAst hashTableAst, bool isCursorInString) @@ -1138,6 +1681,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, @@ -1169,6 +1827,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 @@ -1198,7 +1862,7 @@ bool TryGetResultForSet(Type typeConstraint, ValidateSetAttribute setConstraint, // If the assignment itself was unconstrained, the variable still might be if (!TryGetTypeConstraintOnVariable(completionContext, variableAst.VariablePath.UserPath, out typeConstraint, out setConstraint)) { - return false; + return TryGetInferredCompletionsForAssignment(variableAst, completionContext, out completions); } // Again try the [ValidateSet()] constraint first @@ -1299,7 +1963,7 @@ private static List GetResultForEnum( return GetMatchedResults(allNames, completionContext); } - private List GetResultForEnumPropertyValueOfDSCResource( + private static List GetResultForEnumPropertyValueOfDSCResource( CompletionContext completionContext, string stringToComplete, ref int replacementIndex, @@ -1384,7 +2048,7 @@ private 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()) { @@ -1396,7 +2060,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( { string completionText = isCursorInString ? value : stringQuote + value + stringQuote; if (hasNewLine) - completionText = completionText + stringQuote; + completionText += stringQuote; result.Add(new CompletionResult( completionText, value, @@ -1424,7 +2088,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( { StringBuilder sb = new StringBuilder("[", 50); sb.Append(dynamicKeywordAst.Keyword.Keyword); - sb.Append("]"); + sb.Append(']'); sb.Append(dynamicKeywordAst.ElementName); var resource = sb.ToString(); if (!existingValues.Contains(resource, StringComparer.OrdinalIgnoreCase) && @@ -1447,7 +2111,7 @@ private List GetResultForEnumPropertyValueOfDSCResource( { string completionText = isCursorInString ? resource : stringQuote + resource + stringQuote; if (hasNewLine) - completionText = completionText + stringQuote; + completionText += stringQuote; result.Add(new CompletionResult( completionText, resource, @@ -1466,111 +2130,65 @@ private 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); - if (!shouldContinue || (result != null && result.Count > 0)) + // Check for switch case completion on $PSBoundParameters.Keys + completionContext.WordToComplete = strValue; + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) { - return result; + return switchCaseResult; } - if (strType == StringConstantType.DoubleQuoted) + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) { - 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; - } + return psBoundResult; } - // Handle variable/member completion - if (subInput != null) + bool shouldContinue; + List result = GetResultForEnumPropertyValueOfDSCResource(completionContext, strValue, ref replacementIndex, ref replacementLength, out shouldContinue); + if (!shouldContinue || (result != null && result.Count > 0)) { - int stringStartIndex = tokenAtCursor.Extent.StartScriptPosition.Offset; - int cursorIndexInString = _cursorPosition.Offset - stringStartIndex - 1; - if (cursorIndexInString >= strValue.Length) - cursorIndexInString = strValue.Length; + return result; + } - var analysis = new CompletionAnalysis(_ast, _tokens, _cursorPosition, _options); - var subContext = analysis.CreateCompletionContext(completionContext.TypeInferenceContext); + var commandElementAst = lastAst as CommandElementAst; + string wordToComplete = + CompletionCompleters.ConcatenateStringPathArguments(commandElementAst, string.Empty, completionContext); - var subResult = analysis.GetResultHelper(subContext, out int subReplaceIndex, out _, true); + if (wordToComplete != null) + { + completionContext.WordToComplete = wordToComplete; - 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) + // Try command name completion only if the text contains '-' + if (wordToComplete.Contains('-')) { - result = CompletionCompleters.CompleteCommandArgument(completionContext); - replacementIndex = completionContext.ReplacementIndex; - replacementLength = completionContext.ReplacementLength; - } - // Handle scenarios like this: "c:\wind". Treat the StringLiteral/StringExpandable as path/command - else - { - // 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); } } } @@ -1586,7 +2204,7 @@ private List GetResultForString(CompletionContext completionCo /// /// /// - private ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( + private static ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( IScriptPosition cursorPosition, Ast ast, out DynamicKeywordStatementAst keywordAst) @@ -1622,7 +2240,7 @@ private ConfigurationDefinitionAst GetAncestorConfigurationAstAndKeywordAst( /// /// /// - private List GetResultForIdentifierInConfiguration( + private static List GetResultForIdentifierInConfiguration( CompletionContext completionContext, ConfigurationDefinitionAst configureAst, DynamicKeywordStatementAst keywordAst, @@ -1664,11 +2282,18 @@ private List GetResultForIdentifierInConfiguration( foreach (var keyword in matchedResults) { - string usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); - if (results == null) + string usageString = string.Empty; + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) { - results = new List(); + usageString = dscSubsystem.GetDSCResourceUsageString(keyword); } + else + { + usageString = Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.GetDSCResourceUsageString(keyword); + } + + results ??= new List(); results.Add(new CompletionResult( keyword.Keyword, @@ -1681,15 +2306,34 @@ private 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; + // Check for switch case completion on $PSBoundParameters.Keys + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) + { + return switchCaseResult; + } + + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + + if (lastAst.Parent is BreakStatementAst || lastAst.Parent is ContinueStatementAst) + { + return CompleteLoopLabel(completionContext); + } + var strConst = lastAst as StringConstantExpressionAst; if (strConst != null) { @@ -1709,7 +2353,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: @@ -1745,76 +2394,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 ErrorExpressionAst && cursorAst.Parent is 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) @@ -1884,8 +2558,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) { @@ -1897,8 +2569,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) @@ -1949,7 +2637,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 @@ -1975,9 +2663,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)) { @@ -1989,7 +2674,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) @@ -2033,41 +2718,56 @@ private List GetResultForIdentifier(CompletionContext completi result = CompletionCompleters.CompleteCommandArgument(completionContext); replacementIndex = completionContext.ReplacementIndex; replacementLength = completionContext.ReplacementLength; + return result; } - private List GetResultForAttributeArgument(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength) + private static List GetResultForAttributeArgument(CompletionContext completionContext, ref int replacementIndex, ref int replacementLength) { // 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)) { @@ -2131,5 +2831,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 b6c7d88a111..80cead788d3 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -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 { @@ -61,7 +64,6 @@ public static IEnumerable CompleteCommand(string commandName) /// /// /// - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static IEnumerable CompleteCommand(string commandName, string moduleName, CommandTypes commandTypes = CommandTypes.All) { var runspace = Runspace.DefaultRunspace; @@ -86,7 +88,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 +196,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 +233,7 @@ internal static CompletionResult GetCommandNameCompletionResult(string name, obj syntax = string.IsNullOrEmpty(syntax) ? name : syntax; bool needAmpersand; - if (CompletionRequiresQuotes(name, false)) + if (CompletionHelpers.CompletionRequiresQuotes(name)) { needAmpersand = quote == string.Empty && addAmpersandIfNecessary; string quoteInUse = quote == string.Empty ? "'" : quote; @@ -326,53 +328,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 +420,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 +440,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); + + // 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 += "*"; + } - if (!moduleName.EndsWith('*')) + 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 +479,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); + } + + var completedModules = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (PSObject item in psObjects) + { + var moduleInfo = (PSModuleInfo)item.BaseObject; + var completionText = moduleInfo.Name; + if (!completedModules.Add(completionText)) + { + continue; + } - if (CompletionRequiresQuotes(completionText, false)) + if (shortNameSearch + && completionText.Contains('.') + && !shortNamePattern.IsMatch(completionText.Substring(completionText.LastIndexOf('.') + 1)) + && !shortNamePattern.IsMatch(completionText)) { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; + // 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; } - else + + if (nestedModulesToFilterOut is not null + && nestedModulesToFilterOut.Contains(moduleInfo)) { - completionText = quote + completionText + quote; + 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)); } } @@ -480,7 +540,7 @@ internal static List CompleteModuleName(CompletionContext cont #endregion Module Names #region Command Parameters - private static string[] s_parameterNamesOfImportDSCResource = { "Name", "ModuleName", "ModuleVersion" }; + private static readonly string[] s_parameterNamesOfImportDSCResource = { "Name", "ModuleName", "ModuleVersion" }; internal static List CompleteCommandParameter(CompletionContext context) { @@ -494,8 +554,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; } @@ -511,9 +570,9 @@ internal static List CompleteCommandParameter(CompletionContex && !string.IsNullOrWhiteSpace(context.WordToComplete) && context.WordToComplete.StartsWith('-')) { var lastAst = context.RelatedAsts.Last(); - var wordToMatch = context.WordToComplete.Substring(1) + "*"; + 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 +591,7 @@ internal static List CompleteCommandParameter(CompletionContex return result; } + bool bindPositionalParameters = true; if (parameterAst != null) { // Parent must be a command @@ -542,8 +602,7 @@ internal static List CompleteCommandParameter(CompletionContex else { // No CommandParameterAst is found. It could be a StringConstantExpressionAst "-" - var dashAst = (context.RelatedAsts[context.RelatedAsts.Count - 1] as StringConstantExpressionAst); - if (dashAst == null) + if (context.RelatedAsts[context.RelatedAsts.Count - 1] is not StringConstantExpressionAst dashAst) return result; if (!dashAst.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase)) return result; @@ -551,10 +610,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) @@ -596,6 +669,11 @@ private static List GetParameterCompletionResults(string param { Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed"); List result = new List(); + Assembly commandAssembly = null; + if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo) + { + commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly; + } if (parameterName == string.Empty) { @@ -603,7 +681,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -625,7 +704,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); } return result; @@ -640,7 +720,8 @@ private static List GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.BoundParameters.Values, - withColon); + withColon, + commandAssembly); } return result; @@ -696,29 +777,101 @@ 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, + commandAssembly); + return result; + } + MergedCompiledCommandParameter param = bindingInfo.BoundParameters[matchedParameterName]; WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase); string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + + string helpMessage = string.Empty; + if (param.Parameter.CompiledAttributes is not null) + { + foreach (Attribute attr in param.Parameter.CompiledAttributes) + { + if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + { + helpMessage = $" - {attrHelpMessage}"; + break; + } + } + } + string colonSuffix = withColon ? ":" : string.Empty; if (pattern.IsMatch(matchedParameterName)) { - string completionText = "-" + matchedParameterName + colonSuffix; - string tooltip = parameterType + matchedParameterName; + string completionText = $"-{matchedParameterName}{colonSuffix}"; + string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}"; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } - - // 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}{helpMessage}")); + } + } + } return result; } +#nullable enable + /// + /// Try and get the help message text for the parameter attribute. + /// + /// The attribute to check for the help message. + /// The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in. + /// The help message if it was found otherwise null. + /// True if the help message was set or false if not.> + private static bool TryGetParameterHelpMessage( + ParameterAttribute attr, + Assembly? assembly, + [NotNullWhen(true)] out string? message) + { + message = null; + + if (attr.HelpMessage is not null) + { + message = attr.HelpMessage; + return true; + } + + if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null) + { + return false; + } + + try + { + message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId); + return message is not null; + } + catch (Exception) + { + return false; + } + } +#nullable disable + /// /// Get the parameter completion results by using the given valid parameter sets and available parameters. /// @@ -726,12 +879,14 @@ where pattern.IsMatch(alias) /// /// /// + /// Optional assembly used to lookup parameter help messages. /// private static List GetParameterCompletionResults( string parameterName, uint validParameterSetFlags, IEnumerable parameters, - bool withColon) + bool withColon, + Assembly commandAssembly = null) { var result = new List(); var commonParamResult = new List(); @@ -747,6 +902,7 @@ private static List GetParameterCompletionResults( string name = param.Parameter.Name; string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + string helpMessage = null; bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase); List listInUse = isCommonParameter ? commonParamResult : result; @@ -762,33 +918,45 @@ private static List GetParameterCompletionResults( { foreach (var attr in compiledAttributes) { - var pattr = attr as ParameterAttribute; - if (pattr != null && pattr.DontShow) + if (attr is ParameterAttribute pattr) { - showToUser = false; - addCommonParameters = false; - break; + if (pattr.DontShow) + { + showToUser = false; + addCommonParameters = false; + break; + } + + if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + { + helpMessage = $" - {attrHelpMessage}"; + } } } } if (showToUser) { - string completionText = "-" + name + colonSuffix; - string tooltip = type + name; + string completionText = $"-{name}{colonSuffix}"; + string tooltip = $"{type}{name}{helpMessage}"; listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName, tooltip)); } } - - 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)); + } + } } } @@ -879,7 +1047,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; @@ -1210,7 +1378,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; @@ -1259,7 +1427,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) @@ -1329,7 +1497,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) @@ -1574,13 +1743,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 @@ -1592,36 +1768,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) @@ -1657,9 +1842,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 @@ -1682,78 +1867,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)) { @@ -1778,23 +1909,8 @@ private static void ProcessParameter( { string realEntry = entry; string completionText = entry; - if (quote == string.Empty) - { - if (CompletionRequiresQuotes(entry, false)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - completionText = "'" + realEntry + "'"; - } - } - else - { - if (quote.Equals("'", StringComparison.OrdinalIgnoreCase)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - } - completionText = quote + realEntry + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); } @@ -1804,52 +1920,117 @@ private static void ProcessParameter( } } - NativeCommandArgumentCompletion(commandName, parameter.Parameter, result, commandAst, context, boundArguments); - } - - private static IEnumerable NativeCommandArgumentCompletion_InferTypesOfArgument( - Dictionary boundArguments, - CommandAst commandAst, - CompletionContext context, - string parameterName) - { - if (boundArguments == null) + if (parameterType.IsEnum) { - yield break; - } + RemoveLastNullCompletionResult(result); - AstParameterArgumentPair astParameterArgumentPair; - if (!boundArguments.TryGetValue(parameterName, out astParameterArgumentPair)) - { - yield break; - } + IEnumerable enumValues = LanguagePrimitives.EnumSingleTypeConverter.GetEnumValues(parameterType); - Ast argumentAst = null; - switch (astParameterArgumentPair.ParameterArgumentType) - { - case AstParameterArgumentType.AstPair: + // Exclude values not accepted by ValidateRange-attributes + foreach (ValidateArgumentsAttribute att in parameter.Parameter.ValidationAttributes) + { + if (att is ValidateRangeAttribute rangeAtt) { - AstPair astPair = (AstPair)astParameterArgumentPair; - argumentAst = astPair.Argument; + enumValues = rangeAtt.GetValidatedElements(enumValues); } + } - break; + string wordToComplete = context.WordToComplete ?? string.Empty; + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); - case AstParameterArgumentType.PipeObject: + 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)) { - var pipelineAst = commandAst.Parent as PipelineAst; - if (pipelineAst != null) - { - int i; - for (i = 0; i < pipelineAst.PipelineElements.Count; i++) - { - if (pipelineAst.PipelineElements[i] == commandAst) - break; - } + string completionText = quote == string.Empty ? name : quote + name + quote; + fullMatch = new CompletionResult(completionText, name, CompletionResultType.ParameterValue, name); + continue; + } - if (i != 0) - { - argumentAst = pipelineAst.PipelineElements[i - 1]; + 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); + } + + private static IEnumerable NativeCommandArgumentCompletion_InferTypesOfArgument( + Dictionary boundArguments, + CommandAst commandAst, + CompletionContext context, + string parameterName) + { + if (boundArguments == null) + { + yield break; + } + + AstParameterArgumentPair astParameterArgumentPair; + if (!boundArguments.TryGetValue(parameterName, out astParameterArgumentPair)) + { + yield break; + } + + Ast argumentAst = null; + switch (astParameterArgumentPair.ParameterArgumentType) + { + case AstParameterArgumentType.AstPair: + { + AstPair astPair = (AstPair)astParameterArgumentPair; + argumentAst = astPair.Argument; + } + + break; + + case AstParameterArgumentType.PipeObject: + { + var pipelineAst = commandAst.Parent as PipelineAst; + if (pipelineAst != null) + { + int i; + for (i = 0; i < pipelineAst.PipelineElements.Count; i++) + { + if (pipelineAst.PipelineElements[i] == commandAst) + break; + } + + if (i != 0) + { + argumentAst = pipelineAst.PipelineElements[i - 1]; } } } @@ -1895,7 +2076,7 @@ private static IEnumerable NativeCommandArgumentCompletion_InferType yield return new PSTypeName(pso.TypeNames[0]); } - if (!(pso.BaseObject is PSCustomObject)) + if (pso.BaseObject is not PSCustomObject) { yield return new PSTypeName(pso.BaseObject.GetType()); } @@ -1997,7 +2178,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() @@ -2031,19 +2212,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 @@ -2085,6 +2264,12 @@ private static void NativeCommandArgumentCompletion( break; } + if (parameterName.Equals("ExcludeModule", StringComparison.OrdinalIgnoreCase)) + { + NativeCompletionGetCommand(context, moduleName: null, parameterName, result); + break; + } + if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { var moduleNames = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "Module"); @@ -2123,6 +2308,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)) @@ -2276,7 +2477,7 @@ private static void NativeCommandArgumentCompletion( { if (parameterName.Equals("MemberName", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName], propertiesOnly: false); } break; @@ -2285,14 +2486,32 @@ 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": case "Format-Wide": { - if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) + if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase) + || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase)) + { + NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); + } + else if (parameterName.Equals("View", StringComparison.OrdinalIgnoreCase)) { - NativeCompletionMemberName(context, result, commandAst); + NativeCompletionFormatViewName(context, boundArguments, result, commandAst, commandName); } break; @@ -2303,7 +2522,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; @@ -2325,8 +2544,22 @@ private static void NativeCommandArgumentCompletion( case "Invoke-CimMethod": case "New-CimInstance": case "Register-CimIndicationEvent": + case "Set-CimInstance": { - NativeCompletionCimCommands(parameterName, boundArguments, result, commandAst, context); + // 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; } @@ -2405,9 +2638,8 @@ private static ScriptBlock GetCustomArgumentCompleter( } } - var registeredCompleters = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase) - ? context.NativeArgumentCompleters - : context.CustomArgumentCompleters; + bool isNative = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase); + var registeredCompleters = isNative ? context.NativeArgumentCompleters : context.CustomArgumentCompleters; if (registeredCompleters != null) { @@ -2418,6 +2650,13 @@ private static ScriptBlock GetCustomArgumentCompleter( return scriptBlock; } } + + // For a native command, if a fallback completer is registered, then return it. + // For example, the 'Microsoft.PowerShell.UnixTabCompletion' module. + if (isNative && registeredCompleters.TryGetValue(RegisterArgumentCompleterCommand.FallbackCompleterKey, out scriptBlock)) + { + return scriptBlock; + } } return null; @@ -2436,14 +2675,17 @@ private static bool InvokeScriptArgumentCompleter( scriptBlock, new object[] { commandName, parameterName, wordToComplete, commandAst, GetBoundArgumentsAsHashtable(context) }, resultList); - if (result) - { - resultList.Add(CompletionResult.Null); - } return result; } + /// + /// Invoke the custom argument completer and process its return values. + /// If we consider the completion successful, we add a null instance of the type 'CompletionResult' + /// to the end of the 'result' list to indicate that the argument completion has been processed, so we + /// will not go through the default argument completion even if the 'result' list is still empty. + /// + /// 'true' if the argument completion was successful. 'false' otherwise. private static bool InvokeScriptArgumentCompleter( ScriptBlock scriptBlock, object[] argumentsToCompleter, @@ -2463,20 +2705,44 @@ private static bool InvokeScriptArgumentCompleter( return false; } + if (customResults.Count is 1 && customResults[0] is { BaseObject: "" } or null) + { + // If the script block returns a single empty string or a null value, we will treat it as if it has + // completed successfully but has no results to return. + // This allows a custom completer to suppress the default completions that we may fall back otherwise. + result.Add(CompletionResult.Null); + return true; + } + + int initialCount = result.Count; + foreach (var customResult in customResults) { - var resultAsCompletion = customResult.BaseObject as CompletionResult; - if (resultAsCompletion != null) + if (customResult is null) + { + continue; + } + + if (customResult.BaseObject is CompletionResult resultAsCompletion) { result.Add(resultAsCompletion); continue; } var resultAsString = customResult.ToString(); - result.Add(new CompletionResult(resultAsString)); + if (!string.IsNullOrEmpty(resultAsString)) + { + result.Add(new CompletionResult(resultAsString)); + } } - return true; + bool success = result.Count > initialCount; + if (success) + { + result.Add(CompletionResult.Null); + } + + return success; } // All the methods for native command argument completion will add a null instance of the type CompletionResult to the end of the @@ -2484,9 +2750,9 @@ private static bool InvokeScriptArgumentCompleter( // and has been processed already. So if the "result" list is still empty afterward, we will not go through the default argument completion anymore. #region Native Command Argument Completion - private static void RemoveLastNullCompletionResult(List result) + internal static void RemoveLastNullCompletionResult(List result) { - if (result.Count > 0 && result[result.Count - 1].Equals(CompletionResult.Null)) + if (result?.Count > 0 && result[^1].Equals(CompletionResult.Null)) { result.RemoveAt(result.Count - 1); } @@ -2497,7 +2763,9 @@ private static void NativeCompletionCimCommands( Dictionary boundArguments, List result, CommandAst commandAst, - CompletionContext context) + CompletionContext context, + HashSet excludedValues, + string commandName) { if (boundArguments != null) { @@ -2518,6 +2786,7 @@ private static void NativeCompletionCimCommands( } } + RemoveLastNullCompletionResult(result); if (parameter.Equals("Namespace", StringComparison.OrdinalIgnoreCase)) { NativeCompletionCimNamespace(result, context); @@ -2563,6 +2832,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); + } } } @@ -2570,7 +2849,7 @@ private static void NativeCompletionCimCommands( } } - private static ConcurrentDictionary> s_cimNamespaceAndClassNameToAssociationResultClassNames = + private static readonly ConcurrentDictionary> s_cimNamespaceAndClassNameToAssociationResultClassNames = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); private static IEnumerable NativeCompletionCimAssociationResultClassName_GetResultClassNames( @@ -2599,7 +2878,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; } @@ -2628,7 +2907,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( @@ -2659,7 +2938,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; @@ -2667,11 +2946,11 @@ private static void NativeCompletionCimMethodName( StringBuilder tooltipText = new StringBuilder(); tooltipText.Append(methodName); - tooltipText.Append("("); + tooltipText.Append('('); 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) { @@ -2688,7 +2967,7 @@ private static void NativeCompletionCimMethodName( } tooltipText.Append(CimInstanceAdapter.CimTypeToTypeNameDisplayString(methodParameter.CimType)); - tooltipText.Append(" "); + tooltipText.Append(' '); tooltipText.Append(methodParameter.Name); if (outParameter) @@ -2697,15 +2976,91 @@ private static void NativeCompletionCimMethodName( } } - tooltipText.Append(")"); + tooltipText.Append(')'); 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 ConcurrentDictionary> s_cimNamespaceToClassNames = + private static readonly ConcurrentDictionary> s_cimNamespaceToClassNames = new ConcurrentDictionary>(StringComparer.OrdinalIgnoreCase); private static IEnumerable NativeCompletionCimClassName_GetClassNames(string targetNamespace) @@ -2777,7 +3132,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); @@ -2803,8 +3158,7 @@ private static void NativeCompletionCimNamespace( continue; } - string childNamespace = namespaceNameProperty.Value as string; - if (childNamespace == null) + if (namespaceNameProperty.Value is not string childNamespace) { continue; } @@ -2822,7 +3176,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) @@ -2849,39 +3203,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) @@ -2921,7 +3282,7 @@ private static void NativeCompletionEventLogCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var logName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref logName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref logName); if (!logName.EndsWith('*')) { @@ -2943,17 +3304,7 @@ private static void NativeCompletionEventLogCommands(CompletionContext context, var completionText = eventLog.Log.ToString(); var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); if (pattern.IsMatch(listItemText)) { @@ -2972,7 +3323,7 @@ private static void NativeCompletionJobCommands(CompletionContext context, strin return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3034,17 +3385,7 @@ private static void NativeCompletionJobCommands(CompletionContext context, strin var completionText = psJob.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3059,7 +3400,7 @@ private static void NativeCompletionScheduledJobCommands(CompletionContext conte return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3109,17 +3450,7 @@ private static void NativeCompletionScheduledJobCommands(CompletionContext conte var completionText = psJob.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3193,7 +3524,7 @@ private static void NativeCompletionProcessCommands(CompletionContext context, s return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3249,17 +3580,7 @@ private static void NativeCompletionProcessCommands(CompletionContext context, s continue; uniqueSet.Add(completionText); - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); // on macOS, system processes names will be empty if PowerShell isn't run as `sudo` if (string.IsNullOrEmpty(listItemText)) @@ -3284,7 +3605,7 @@ private static void NativeCompletionProviderCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var providerName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref providerName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref providerName); if (!providerName.EndsWith('*')) { @@ -3302,17 +3623,7 @@ private static void NativeCompletionProviderCommands(CompletionContext context, var completionText = providerInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3328,7 +3639,7 @@ private static void NativeCompletionDriveCommands(CompletionContext context, str RemoveLastNullCompletionResult(result); var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3350,17 +3661,7 @@ private static void NativeCompletionDriveCommands(CompletionContext context, str var completionText = driveInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3375,7 +3676,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3401,17 +3702,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s var completionText = serviceInfo.DisplayName; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3432,17 +3723,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s var completionText = serviceInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3462,7 +3743,7 @@ private static void NativeCompletionVariableCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var variableName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref variableName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref variableName); if (!variableName.EndsWith('*')) { variableName += "*"; @@ -3488,7 +3769,7 @@ private static void NativeCompletionVariableCommands(CompletionContext context, completionText = completionText.Replace("*", "`*"); } - if (!completionText.Equals("$", StringComparison.Ordinal) && CompletionRequiresQuotes(completionText, false)) + if (!completionText.Equals("$", StringComparison.Ordinal) && CompletionHelpers.CompletionRequiresQuotes(completionText)) { var quoteInUse = effectiveQuote == string.Empty ? "'" : effectiveQuote; if (quoteInUse == "'") @@ -3521,7 +3802,7 @@ private static void NativeCompletionAliasCommands(CompletionContext context, str if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { var commandName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref commandName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref commandName); if (!commandName.EndsWith('*')) { @@ -3538,17 +3819,7 @@ private static void NativeCompletionAliasCommands(CompletionContext context, str var completionText = aliasInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3582,7 +3853,7 @@ private static void NativeCompletionTraceSourceCommands(CompletionContext contex RemoveLastNullCompletionResult(result); var traceSourceName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref traceSourceName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref traceSourceName); if (!traceSourceName.EndsWith('*')) { @@ -3601,17 +3872,7 @@ private static void NativeCompletionTraceSourceCommands(CompletionContext contex var completionText = trace.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3630,7 +3891,7 @@ private static void NativeCompletionSetLocationCommand(CompletionContext context RemoveLastNullCompletionResult(result); - context.WordToComplete = context.WordToComplete ?? string.Empty; + context.WordToComplete ??= string.Empty; var clearLiteralPath = false; if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)) { @@ -3736,7 +3997,7 @@ private static void NativeCompletionCopyMoveItemCommand(CompletionContext contex // The parameter Destination for Move-Item and Copy-Item takes literal path RemoveLastNullCompletionResult(result); - context.WordToComplete = context.WordToComplete ?? string.Empty; + context.WordToComplete ??= string.Empty; var clearLiteralPath = TurnOnLiteralPathOption(context); try @@ -3767,7 +4028,7 @@ private static void NativeCompletionPathArgument(CompletionContext context, stri RemoveLastNullCompletionResult(result); - context.WordToComplete = context.WordToComplete ?? string.Empty; + context.WordToComplete ??= string.Empty; var clearLiteralPath = false; if (paramName.Equals("LiteralPath", StringComparison.OrdinalIgnoreCase)) { @@ -3789,45 +4050,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. - var pipelineAst = commandAst.Parent as PipelineAst; - if (pipelineAst == null) - 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); } @@ -3955,9 +4303,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 }; } } @@ -4089,7 +4439,9 @@ private static ArgumentLocation FindTargetArgumentLocation(Collection 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; + + default: + stringType = StringConstantType.SingleQuoted; + break; + } - var notHiddenEntries = new HashSet(StringComparer.OrdinalIgnoreCase); - string providerPath = null; + var useLiteralPath = context.GetOption("LiteralPaths", @default: false); + if (useLiteralPath) + { + basePath = EscapePath(basePath, stringType, useLiteralPath, out _); + } - 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; - } + PowerShell currentPS = context.Helper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") + .AddParameter("Path", basePath); - if (!notHiddenEntries.Contains(providerPath)) - { - notHiddenEntries.Add(providerPath); - } - } + 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; + } - if (leaf != null) - { - leaf = leaf + "*"; - var parentPath = Path.GetDirectoryName(providerPath); + 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; + } - // ProviderPath should be absolute path for FileSystem entries - if (!string.IsNullOrEmpty(parentPath)) - { - string[] entries = null; - try - { - entries = Directory.GetFileSystemEntries(parentPath, leaf, _enumerationOptions); - } - catch (Exception) - { - } + var resolvedPaths = context.Helper.ExecuteCurrentPowerShell(out _); + if (resolvedPaths is null || resolvedPaths.Count == 0) + { + return CommandCompletion.EmptyCompletionResult; + } - if (entries != null) - { - hiddenFilesAreHandled = true; + 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}::"; + } - 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 - } - } - } - } - } - } - } + List results; + switch (resolvedProvider.Name) + { + case FileSystemProvider.ProviderName: + results = GetFileSystemProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + extension, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType, + relativeBasePath); + break; - if (!hiddenFilesAreHandled) - { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-ChildItem") - .AddParameter("Path", wordToComplete + "*") - .AddParameter("Hidden", true); + default: + results = GetDefaultProviderResults( + context, + resolvedProvider, + resolvedPaths, + filter, + containerOnly, + useRelativePath, + useLiteralPath, + inputUsedHomeChar, + providerPrefix, + stringType); + break; + } - var hiddenItems = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (hiddenItems != null && hiddenItems.Count > 0) - { - foreach (var hiddenItem in hiddenItems) - { - psobjs.Add(hiddenItem); - } - } - } - } + 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; + } - // Sorting the results by the path - var sortedPsobjs = psobjs.OrderBy(a => a, new ItemPathComparer()); + foreach (var item in resolvedPaths) + { + var pathInfo = (PathInfo)item.BaseObject; + var dirInfo = new DirectoryInfo(pathInfo.ProviderPath); - foreach (PSObject psobj in sortedPsobjs) + bool baseQuotesNeeded = false; + string basePath; + if (!relativePaths) + { + if (pathInfo.Drive is null) + { + basePath = dirInfo.FullName; + } + else { - object baseObj = PSObject.Base(psobj); - string path = null, providerPath = null; + int stringStartIndex = pathInfo.Drive.Root.EndsWith(provider.ItemSeparator) && pathInfo.Drive.Root.Length > 1 + ? pathInfo.Drive.Root.Length - 1 + : pathInfo.Drive.Root.Length; - // 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) - { - path = baseObjAsPathInfo.Path; - providerPath = baseObjAsPathInfo.ProviderPath; - } - else if (baseObj is FileSystemInfo) - { - // The target provider is the FileSystem - dynamic dirResult = psobj; - providerPath = dirResult.FullName; - path = wordContainsProviderId ? dirResult.PSPath : providerPath; - } - else - { - var baseObjAsString = baseObj as string; - if (baseObjAsString != null) - { - // The target provider is the FileSystem - providerPath = baseObjAsString; - path = wordContainsProviderId - ? FileSystemProvider.ProviderName + "::" + baseObjAsString - : providerPath; - } - } + basePath = pathInfo.Drive.VolumeSeparatedByColon + ? string.Concat(pathInfo.Drive.Name, ":", dirInfo.FullName.AsSpan(stringStartIndex)) + : string.Concat(pathInfo.Drive.Name, dirInfo.FullName.AsSpan(stringStartIndex)); + } - if (path == null) continue; - if (isFileSystem && providerPath == null) continue; + 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); - string completionText; - if (relativePaths) - { - try - { - 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); - } - catch (Exception) - { - // 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; - } - } - else - { - completionText = path; - } + 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 (ProviderSpecified(completionText) && !wordContainsProviderId) + if (basePath is null) + { + basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( + entry.FullName, + relativeBasePath); + if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) { - // Remove the provider id from the path: cd \\scratch2\scratch\dongbw - var index = completionText.IndexOf(':'); - completionText = completionText.Substring(index + 2); + basePath = $".{provider.ItemSeparator}{basePath}"; } - if (CompletionRequiresQuotes(completionText, !useLiteralPath)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - { - completionText = completionText.Replace("'", "''"); - } - else - { - // 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("$", "`$"); - } + basePath = basePath.Remove(basePath.Length - entry.Name.Length); + basePath = RebuildPathWithVars(basePath, homePath, stringType, literalPaths, out baseQuotesNeeded); + } - if (!useLiteralPath) - { - if (quoteInUse == "'") - { - completionText = completionText.Replace("[", "`["); - completionText = completionText.Replace("]", "`]"); - } - else - { - completionText = completionText.Replace("[", "``["); - completionText = completionText.Replace("]", "``]"); - } - } + var resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; - completionText = quoteInUse + completionText + quoteInUse; - } - else if (quote != string.Empty) + 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) { - completionText = quote + completionText + quote; + continue; } - if (isFileSystem) - { - // Use .NET APIs directly to reduce the time overhead - var isContainer = Directory.Exists(providerPath); - if (containerOnly && !isContainer) - continue; + resultType = isContainer + ? CompletionResultType.ProviderContainer + : CompletionResultType.ProviderItem; + } + else + { + resultType = CompletionResultType.Text; + } - if (!containerOnly && !isContainer && !CheckFileExtension(providerPath, extension)) - continue; + 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(); + } - string tooltip = providerPath, listItemText = Path.GetFileName(providerPath); - results.Add(new CompletionResult(completionText, listItemText, - isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem, - tooltip)); + 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 { - 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; - } + backtickCount = sb[^1] == '`' ? 4 : 2; + } - if (string.IsNullOrEmpty(listItemText)) - { - // For provider items that don't have PSChildName values, such as variable::error - listItemText = item.Name; - } + _ = sb.Append('`', backtickCount); + quotesAreNeeded = true; + } + 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 '`': + // 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 results; + 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 { @@ -4501,52 +5217,63 @@ private struct SHARE_INFO_1 public string remark; } - 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; - - private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + 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 }; - [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); - internal static List GetFileShares(string machine, bool ignoreHidden) { #if UNIX return new List(); #else - IntPtr shBuf; - uint numEntries; + nint shBuf = nint.Zero; + uint numEntries = 0; uint totalEntries; uint resumeHandle = 0; - int result = NetShareEnum(machine, 1, out shBuf, - MAX_PREFERRED_LENGTH, out numEntries, out totalEntries, - ref resumeHandle); - - var shares = new List(); - if (result == NERR_Success || result == ERROR_MORE_DATA) + try { - for (int i = 0; i < numEntries; ++i) + 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) { - IntPtr curInfoPtr = (IntPtr)((long)shBuf + (Marshal.SizeOf() * i)); - SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); + for (int i = 0; i < numEntries; ++i) + { + nint curInfoPtr = shBuf + (Marshal.SizeOf() * i); + SHARE_INFO_1 shareInfo = Marshal.PtrToStructure(curInfoPtr); - if ((shareInfo.type & STYPE_MASK) != STYPE_DISKTREE) - continue; - if (ignoreHidden && shareInfo.netname.EndsWith('$')) - continue; - shares.Add(shareInfo.netname); + if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + { + continue; + } + + if (ignoreHidden && shareInfo.netname.EndsWith('$')) + { + continue; + } + + shares.Add(shareInfo.netname); + } } - } - return shares; + return shares; + } + finally + { + if (shBuf != nint.Zero) + { + Interop.Windows.NetApiBufferFree(shBuf); + } + } #endif } @@ -4581,31 +5308,51 @@ public static IEnumerable CompleteVariable(string variableName return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); } - private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" }; + private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:", "Using:" }; - private static readonly char[] s_charactersRequiringQuotes = new char[] { - '-', '`', '&', '@', '\'', '"', '#', '{', '}', '(', ')', '$', ',', ';', '|', '<', '>', ' ', '.', '\\', '/', '\t', '^', - }; + private static readonly SearchValues s_charactersRequiringQuotes = SearchValues.Create("-`&@'\"#{}()$,;|<> .\\/ \t^"); + + private static bool ContainsCharactersRequiringQuotes(ReadOnlySpan text) + => text.ContainsAny(s_charactersRequiringQuotes); internal static List CompleteVariable(CompletionContext context) { - HashSet hashedResults = new HashSet(StringComparer.OrdinalIgnoreCase); - List results = new List(); + 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?.Last(); + 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 != null) + if (lastAst is not null) { Ast parent = lastAst.Parent; - var findVariablesVisitor = new FindVariablesVisitor { CompletionVariableAst = lastAst }; + var findVariablesVisitor = new FindVariablesVisitor + { + CompletionVariableAst = lastAst, + StopSearchOffset = lastAst.Extent.StartOffset, + Context = context.TypeInferenceContext + }; while (parent != null) { if (parent is IParameterMetadataProvider) @@ -4617,216 +5364,740 @@ internal static List CompleteVariable(CompletionContext contex parent = parent.Parent; } - foreach (Tuple varAst in findVariablesVisitor.VariableSources) + foreach (string varName in findVariablesVisitor.FoundVariables) { - Ast astTarget = null; - string userPath = null; + if (!wildcardPattern.IsMatch(varName)) + { + continue; + } - VariableExpressionAst variableDefinitionAst = varAst.Item2 as VariableExpressionAst; - if (variableDefinitionAst != null) + VariableInfo varInfo = findVariablesVisitor.VariableInfoTable[varName]; + PSTypeName varType = varInfo.LastDeclaredConstraint ?? varInfo.LastAssignedType; + string toolTip; + if (varType is null) { - userPath = varAst.Item1; - astTarget = varAst.Item2.Parent; + toolTip = varName; } else { - CommandAst commandParameterAst = varAst.Item2 as CommandAst; - if (commandParameterAst != null) - { - userPath = varAst.Item1; - astTarget = varAst.Item2; - } + toolTip = varType.Type is not null + ? StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(varType.Type, dropNamespaces: true), varName) + : varType.Name; } - if (string.IsNullOrEmpty(userPath)) - { - Diagnostics.Assert(false, "Found a variable source but it was an unknown AST type."); - } + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(varName) + ? prefix + scopePrefix + varName + : prefix + "{" + scopePrefix + varName + "}"; + AddUniqueVariable(hashedResults, results, completionText, varName, toolTip); + } + } - if (wildcardPattern.IsMatch(userPath)) + if (colon == -1) + { + var allVariables = context.ExecutionContext.SessionState.Internal.GetVariableTable(); + foreach (var key in allVariables.Keys) + { + if (wildcardPattern.IsMatch(key)) { - var completedName = (userPath.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + userPath - : prefix + "{" + userPath + "}"; - var tooltip = userPath; - var ast = astTarget; - - while (ast != null) + 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)) { - 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); - } - - break; - } - - var assignmentAst = ast.Parent as AssignmentStatementAst; - if (assignmentAst != null) - { - if (assignmentAst.Left == ast) - { - tooltip = ast.Extent.Text; - } - - break; - } - - 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); - } - - break; - } - - ast = ast.Parent; + toolTip += $" - {variable.Description}"; } - AddUniqueVariable(hashedResults, results, completedName, userPath, tooltip); + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + name + : prefix + "{" + name + "}"; + AddUniqueVariable(hashedResults, tempResults, completionText, key, toolTip); } } - } - string pattern; - string provider; - if (colon == -1) - { - pattern = "variable:" + wordToComplete + "*"; - provider = string.Empty; + if (tempResults.Count > 0) + { + results.AddRange(tempResults.OrderBy(item => item.ListItemText, StringComparer.OrdinalIgnoreCase)); + tempResults.Clear(); + } } else { - provider = wordToComplete.Substring(0, colon + 1); - if (s_variableScopes.Contains(provider, StringComparer.OrdinalIgnoreCase)) + string pattern; + if (s_variableScopes.Contains(scopePrefix, StringComparer.OrdinalIgnoreCase)) { - pattern = "variable:" + wordToComplete.Substring(colon + 1) + "*"; + pattern = string.Concat("variable:", wordToComplete, "*"); } else { - pattern = wordToComplete + "*"; + 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 powerShellExecutionHelper = context.Helper; + powerShellExecutionHelper + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", pattern) + .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Name"); - Exception exceptionThrown; - var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) - { - foreach (dynamic psobj in psobjs) + var psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out _); + + if (psobjs is not null) { - var name = psobj.Name as string; - if (!string.IsNullOrEmpty(name)) + foreach (dynamic psobj in psobjs) { - var tooltip = name; - var variable = PSObject.Base(psobj) as PSVariable; - if (variable != null) + var name = psobj.Name as string; + if (!string.IsNullOrEmpty(name)) { - var value = variable.Value; - if (value != null) + var tooltip = name; + var variable = PSObject.Base(psobj) as PSVariable; + if (variable != null) { - tooltip = StringUtil.Format("[{0}]${1}", - ToStringCodeMethods.Type(value.GetType(), - dropNamespaces: true), name); + 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 = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + provider + name - : prefix + "{" + provider + name + "}"; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + 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)) { - powerShellExecutionHelper - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Get-Item").AddParameter("Path", "env:*") - .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Utility\\Sort-Object").AddParameter("Property", "Key"); + 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); + } - psobjs = powerShellExecutionHelper.ExecuteCurrentPowerShell(out exceptionThrown); - if (psobjs != null) + 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) { - foreach (dynamic psobj in psobjs) + if (wildcardPattern.IsMatch(specialVariable)) { - 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); - } + 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); } } } - // 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 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)); + } + } + + internal static readonly HashSet s_varModificationCommands = new(StringComparer.OrdinalIgnoreCase) + { + "New-Variable", + "nv", + "Set-Variable", + "set", + "sv" + }; + + internal static readonly string[] s_varModificationParameters = new string[] + { + "Name", + "Value" + }; + + 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) { - if (wildcardPattern.IsMatch(specialVariable)) + PSTypeName type; + switch (ast) { - var completedName = (specialVariable.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + specialVariable - : prefix + "{" + specialVariable + "}"; + 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; - AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + case ScriptBlockExpressionAst: + type = new PSTypeName(typeof(ScriptBlock)); + break; + + default: + type = null; + break; } + + return type; } - if (colon == -1) + private void SaveVariableInfo(string variableName, PSTypeName variableType, bool isConstraint) { - // 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) + if (VariableInfoTable.TryGetValue(variableName, out VariableInfo varInfo)) { - foreach (var psobj in psobjs) + if (isConstraint) { - var driveInfo = PSObject.Base(psobj) as PSDriveInfo; - if (driveInfo != null) + 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; + } + + 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; + } + + 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) + { + if (firstConvertExpression is null && attributeChild is ConvertExpressionAst convertExpression) + { + // Multiple type constraint can be set on a variable like this: [int] [string] $Var1 = 1 + // But it's the left most type constraint that determines the final type. + firstConvertExpression = convertExpression; + } + + child = attributeChild.Child; + } + + if (child is VariableExpressionAst variableExpression) + { + if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) + { + return; + } + + if (firstConvertExpression is not null) + { + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(firstConvertExpression.Type.TypeName), isConstraint: true); + } + else + { + PSTypeName lastAssignedType = right is CommandExpressionAst commandExpression + ? GetInferredVarTypeFromAst(commandExpression.Expression) + : null; + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); + } + } + } + else if (left is VariableExpressionAst variableExpression) + { + if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) + { + return; + } + + PSTypeName lastAssignedType; + if (right is CommandExpressionAst commandExpression) + { + lastAssignedType = GetInferredVarTypeFromAst(commandExpression.Expression); + } + else + { + lastAssignedType = null; + } + + 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) { - var name = driveInfo.Name; - if (name != null && !string.IsNullOrWhiteSpace(name) && name.Length > 1) + PSTypeName variableType; + if (bindingResult.BoundParameters.TryGetValue("Value", out ParameterBindingResult variableValue)) + { + variableType = GetInferredVarTypeFromAst(variableValue.Value); + } + else { - var completedName = (name.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + name + ":" - : prefix + "{" + name + ":}"; + variableType = null; + } - var tooltip = string.IsNullOrEmpty(driveInfo.Description) ? name : driveInfo.Description; - AddUniqueVariable(hashedResults, results, completedName, name, tooltip); + SaveVariableInfo(nameValue, variableType, isConstraint: false); + } + } + } + + 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 varName = outVarBind.ConstantValue as string; + if (varName is not null) + { + SaveVariableInfo(varName, new PSTypeName(typeof(ArrayList)), isConstraint: false); + } + } + } + + 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)) + { + var varName = pipeVarBind.ConstantValue as string; + if (varName is not null) + { + var inferredTypes = AstTypeInference.InferTypeOf(commandAst, Context, TypeInferenceRuntimePermissions.AllowSafeEval); + PSTypeName varType = inferredTypes.Count == 0 + ? null + : inferredTypes[0]; + SaveVariableInfo(varName, varType, isConstraint: false); + } } } } } - var scopePattern = WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase); - foreach (var scope in s_variableScopes) + foreach (RedirectionAst redirection in commandAst.Redirections) { - if (scopePattern.IsMatch(scope)) + if (redirection is FileRedirectionAst fileRedirection + && fileRedirection.Location is StringConstantExpressionAst redirectTarget + && redirectTarget.Value.StartsWith("variable:", StringComparison.OrdinalIgnoreCase) + && redirectTarget.Value.Length > "variable:".Length) { - var completedName = (scope.IndexOfAny(s_charactersRequiringQuotes) == -1) - ? prefix + scope - : prefix + "{" + scope + "}"; - AddUniqueVariable(hashedResults, results, completedName, scope, scope); + string varName = redirectTarget.Value.Substring("variable:".Length); + PSTypeName varType; + switch (fileRedirection.FromStream) + { + case RedirectionStream.Error: + varType = new PSTypeName(typeof(ErrorRecord)); + break; + + case RedirectionStream.Warning: + varType = new PSTypeName(typeof(WarningRecord)); + break; + + case RedirectionStream.Verbose: + varType = new PSTypeName(typeof(VerboseRecord)); + break; + + case RedirectionStream.Debug: + varType = new PSTypeName(typeof(DebugRecord)); + break; + + case RedirectionStream.Information: + varType = new PSTypeName(typeof(InformationRecord)); + break; + + default: + varType = null; + break; + } + + SaveVariableInfo(varName, varType, isConstraint: false); + } + } + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitParameter(ParameterAst parameterAst) + { + if (parameterAst.Extent.StartOffset > StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + VariableExpressionAst variableExpression = parameterAst.Name; + if (variableExpression == CompletionVariableAst) + { + return AstVisitAction.Continue; + } + + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(parameterAst.StaticType), isConstraint: true); + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) + { + if (forEachStatementAst.Extent.StartOffset > StopSearchOffset || forEachStatementAst.Variable == CompletionVariableAst) + { + return AstVisitAction.StopVisit; + } + + SaveVariableInfo(forEachStatementAst.Variable.VariablePath.UnqualifiedPath, variableType: null, isConstraint: false); + 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 VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + } + + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + { + if (scriptBlockExpressionAst == Top) + { + return AstVisitAction.Continue; + } + + 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 AstVisitAction.SkipChildren; + } + } + + private static readonly Lazy> s_specialVariablesCache = new(BuildSpecialVariablesCache); + + private static SortedSet BuildSpecialVariablesCache() + { + 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)); + } + } + + return result; + } + + internal static PSTypeName GetLastDeclaredTypeConstraint(VariableExpressionAst variableAst, TypeInferenceContext typeInferenceContext) + { + Ast parent = variableAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor() + { + CompletionVariableAst = variableAst, + StopSearchOffset = variableAst.Extent.StartOffset, + Context = typeInferenceContext + }; + while (parent != null) + { + if (parent is IParameterMetadataProvider) + { + findVariablesVisitor.Top = parent; + parent.Visit(findVariablesVisitor); + } + + if (findVariablesVisitor.VariableInfoTable.TryGetValue(variableAst.VariablePath.UserPath, out VariableInfo varInfo) + && varInfo.LastDeclaredConstraint is not null) + { + return varInfo.LastDeclaredConstraint; + } + + parent = parent.Parent; + } + + return null; + } + + #endregion Variables + + #region Comments + + internal static List CompleteComment(CompletionContext context, ref int replacementIndex, ref int replacementLength) + { + if (context.WordToComplete.StartsWith("<#", StringComparison.Ordinal)) + { + return CompleteCommentHelp(context, ref replacementIndex, ref replacementLength); + } + + // Complete #requires statements + if (context.WordToComplete.StartsWith("#requires ", StringComparison.OrdinalIgnoreCase)) + { + return CompleteRequires(context, ref replacementIndex, ref replacementLength); + } + + var results = new List(); + + // Complete the history entries + Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$"); + if (!matchResult.Success) + { + return results; + } + + string wordToComplete = matchResult.Groups[1].Value; + Collection psobjs; + + 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 _); + + if (psobjs != null && psobjs.Count == 1) + { + 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)); + } + } + } + + 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 not 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)); } } } @@ -4834,162 +6105,493 @@ internal static List CompleteVariable(CompletionContext contex return results; } - private static void AddUniqueVariable(HashSet hashedResults, List results, string completionText, string listItemText, string tooltip) + private static List CompleteRequires(CompletionContext context, ref int replacementIndex, ref int replacementLength) { - if (!hashedResults.Contains(completionText)) + var results = new List(); + + int cursorIndex = context.CursorPosition.ColumnNumber - 1; + string lineToCursor = context.CursorPosition.Line.Substring(0, cursorIndex); + + // 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)) { - hashedResults.Add(completionText); - results.Add(new CompletionResult(completionText, listItemText, CompletionResultType.Variable, tooltip)); + return results; + } + + // Regex to find parameter like " -Parameter1" or " -" + MatchCollection hashtableKeyMatches = Regex.Matches(lineToCursor, @"\s+-([A-Za-z]+|$)"); + if (hashtableKeyMatches.Count == 0) + { + return results; + } + + Group currentParameterMatch = hashtableKeyMatches[^1].Groups[1]; + + // Complete the parameter if the cursor is at a parameter + if (currentParameterMatch.Index + currentParameterMatch.Length == cursorIndex) + { + string currentParameterPrefix = currentParameterMatch.Value; + + replacementIndex = context.CursorPosition.Offset - currentParameterPrefix.Length; + replacementLength = currentParameterPrefix.Length; + + // 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; + } + + // Regex to find parameter values (any text that appears after various delimiters) + hashtableKeyMatches = Regex.Matches(lineToCursor, @"(\s+|,|;|{|\""|'|=)(\w+|$)"); + string currentValue; + if (hashtableKeyMatches.Count == 0) + { + currentValue = string.Empty; } + else + { + 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) + { + if (psEditionEntry.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetRequiresPsEditionsToolTip(psEditionEntry); + results.Add(new CompletionResult(psEditionEntry, psEditionEntry, CompletionResultType.ParameterValue, toolTip)); + } + } + + 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) + { + context.WordToComplete = currentValue; + return CompleteModuleName(context, true); + } + + string hashtableString = lineToCursor.Substring(hashtableStart); + + // 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) + { + string existingHashtableKey = existingHashtableKeyMatch.Value.TrimStart(s_hashtableKeyPrefixes); + + if (string.IsNullOrEmpty(existingHashtableKey)) + { + 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; + } + + if (existingHashtableKey.Equals("RequiredVersion", StringComparison.OrdinalIgnoreCase)) + { + moduleSpecKeysToComplete.Remove("ModuleVersion"); + moduleSpecKeysToComplete.Remove("MaximumVersion"); + continue; + } + } + + Group lastHashtableKeyPrefixGroup = hashtableKeyMatches[^1].Groups[0]; + + // 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) + { + if (sawModuleNameLast) + { + 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 results; } - private class FindVariablesVisitor : AstVisitor + private static readonly string[] s_requiresParameters = new string[] { - internal Ast Top; - internal Ast CompletionVariableAst; - internal readonly List> VariableSources = new List>(); + "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; + } - public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) + // Last keyword at or before the cursor + Match lineKeyword = null; + for (int i = usedKeywords.Count - 1; i >= 0; i--) { - if (variableExpressionAst != CompletionVariableAst) + Match keyword = usedKeywords[i]; + if (context.CursorPosition.Offset >= keyword.Index + context.TokenAtCursor.Extent.StartOffset) { - VariableSources.Add(new Tuple(variableExpressionAst.VariablePath.UserPath, variableExpressionAst)); + lineKeyword = keyword; + break; } + } - return AstVisitAction.Continue; + if (lineKeyword is null) + { + return null; } - public override AstVisitAction VisitCommand(CommandAst commandAst) + // Cursor is within or at the start/end of the keyword + if (context.CursorPosition.Offset <= lineKeyword.Index + lineKeyword.Length + context.TokenAtCursor.Extent.StartOffset) { - // 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))) - { - string[] desiredParameters = new string[] { "PV", "PipelineVariable", "OV", "OutVariable" }; + replacementIndex = context.TokenAtCursor.Extent.StartOffset + lineKeyword.Index; + replacementLength = lineKeyword.Value.Length; - StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false, desiredParameters); - if (bindingResult != null) + var validKeywords = new HashSet(s_commentHelpKeywords, StringComparer.OrdinalIgnoreCase); + foreach (Match keyword in usedKeywords) + { + if (keyword == lineKeyword || s_commentHelpAllowedDuplicateKeywords.Contains(keyword.Value)) { - ParameterBindingResult parameterBindingResult; + continue; + } - foreach (string commandVariableParameter in desiredParameters) - { - if (bindingResult.BoundParameters.TryGetValue(commandVariableParameter, out parameterBindingResult)) - { - VariableSources.Add(new Tuple((string)parameterBindingResult.ConstantValue, commandAst)); - } - } + validKeywords.Remove(keyword.Value); + } + + var result = new List(); + foreach (string keyword in validKeywords) + { + if (keyword.StartsWith(lineKeyword.Value, StringComparison.OrdinalIgnoreCase)) + { + string toolTip = GetCommentHelpKeywordsToolTip(keyword); + result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, toolTip)); } } - return AstVisitAction.Continue; + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + // 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; + + replacementIndex = lineStartIndex + argumentIndex; + replacementLength = keywordArgument.Value.Length; + + if (lineKeyword.Value.Equals("PARAMETER", StringComparison.OrdinalIgnoreCase)) { - return functionDefinitionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + return CompleteCommentParameterValue(context, keywordArgument.Value); } - public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) + if (lineKeyword.Value.Equals("FORWARDHELPTARGETNAME", StringComparison.OrdinalIgnoreCase)) { - return scriptBlockExpressionAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + var result = new List(CompleteCommand(keywordArgument.Value, "*", CommandTypes.All)); + return result.Count > 0 ? result : null; } - public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) + if (lineKeyword.Value.Equals("FORWARDHELPCATEGORY", StringComparison.OrdinalIgnoreCase)) { - return scriptBlockAst != Top ? AstVisitAction.SkipChildren : AstVisitAction.Continue; + var result = new List(); + foreach (string category in s_commentHelpForwardCategories) + { + if (category.StartsWith(keywordArgument.Value, StringComparison.OrdinalIgnoreCase)) + { + result.Add(new CompletionResult(category)); + } + } + return result.Count > 0 ? result : null; } - } - - private static readonly Lazy> s_specialVariablesCache = new Lazy>(BuildSpecialVariablesCache); - private static SortedSet BuildSpecialVariablesCache() - { - var result = new SortedSet(); - foreach (var member in typeof(SpecialVariables).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + if (lineKeyword.Value.Equals("REMOTEHELPRUNSPACE", StringComparison.OrdinalIgnoreCase)) { - if (member.FieldType.Equals(typeof(string))) + var result = new List(); + foreach (CompletionResult variable in CompleteVariable(keywordArgument.Value)) { - result.Add((string)member.GetValue(null)); + // 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; } - return result; + if (lineKeyword.Value.Equals("EXTERNALHELP", StringComparison.OrdinalIgnoreCase)) + { + context.WordToComplete = keywordArgument.Value; + var result = new List(CompleteFilename(context, containerOnly: false, (new HashSet() { ".xml" }))); + return result.Count > 0 ? result : null; + } + + return null; } - #endregion Variables + private static readonly string[] s_commentHelpKeywords = new string[] + { + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" + }; - #region Comments + 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 + }; - // Complete the history entries - internal static List CompleteComment(CompletionContext context) + private static readonly HashSet s_commentHelpAllowedDuplicateKeywords = new(StringComparer.OrdinalIgnoreCase) { - List results = new List(); + "EXAMPLE", + "LINK", + "PARAMETER" + }; - Match matchResult = Regex.Match(context.WordToComplete, @"^#([\w\-]*)$"); - if (!matchResult.Success) { return results; } + private static readonly string[] s_commentHelpForwardCategories = new string[] + { + "Alias", + "All", + "Cmdlet", + "ExternalScript", + "FAQ", + "Filter", + "Function", + "General", + "Glossary", + "HelpFile", + "Provider", + "ScriptCommand" + }; - string wordToComplete = matchResult.Groups[1].Value; - Collection psobjs; + private static FunctionDefinitionAst GetCommentHelpFunctionTarget(CompletionContext context) + { + if (context.TokenAtCursor.Kind != TokenKind.Comment) + { + return null; + } - int entryId; - if (Regex.IsMatch(wordToComplete, @"^[0-9]+$") && LanguagePrimitives.TryConvertTo(wordToComplete, out entryId)) + 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) { - context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)).AddParameter("Id", entryId); - psobjs = context.Helper.ExecuteCurrentPowerShell(out _); + // Helpblock before function inside advanced function + if (firstAstAfterComment is not null + && firstAstAfterComment.Extent.StartLineNumber <= commentEndLine + && firstAstAfterComment is FunctionDefinitionAst outerHelpFunctionDefAst) + { + return outerHelpFunctionDefAst; + } - if (psobjs != null && psobjs.Count == 1) + // Helpblock inside function + if (lastAst.Parent.Parent is FunctionDefinitionAst innerHelpFunctionDefAst) { - 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)); - } - } + return innerHelpFunctionDefAst; } + } - return results; + if (lastAst is ScriptBlockAst) + { + // Helpblock before function + if (firstAstAfterComment is not null + && firstAstAfterComment.Extent.StartLineNumber <= commentEndLine + && firstAstAfterComment is FunctionDefinitionAst statement) + { + return statement; + } + + // Advanced function with help inside + if (lastAst.Parent is FunctionDefinitionAst advFuncDefAst) + { + return advFuncDefAst; + } } - wordToComplete = "*" + wordToComplete + "*"; - context.Helper.AddCommandWithPreferenceSetting("Get-History", typeof(GetHistoryCommand)); + return null; + } - psobjs = context.Helper.ExecuteCurrentPowerShell(out _); - var pattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); + private static List CompleteCommentParameterValue(CompletionContext context, string wordToComplete) + { + FunctionDefinitionAst foundFunction = GetCommentHelpFunctionTarget(context); - if (psobjs != null) + ReadOnlyCollection foundParameters = null; + if (foundFunction is not null) { - for (int index = psobjs.Count - 1; index >= 0; index--) + 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; + } + + if (foundParameters is null || foundParameters.Count == 0) + { + return null; + } + + var parametersToShow = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (ParameterAst parameter in foundParameters) + { + if (parameter.Name.VariablePath.UserPath.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) { - var psobj = psobjs[index]; - var historyInfo = PSObject.Base(psobj) as HistoryInfo; - if (historyInfo == null) continue; + parametersToShow.Add(parameter.Name.VariablePath.UserPath); + } + } - 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)); - } + MatchCollection usedParameters = Regex.Matches(context.TokenAtCursor.Text, @"(?<=^\s*\.parameter\s+)\w.*(?<=\S)", RegexOptions.Multiline | RegexOptions.IgnoreCase); + foreach (Match parameter in usedParameters) + { + if (wordToComplete.Equals(parameter.Value, StringComparison.OrdinalIgnoreCase)) + { + 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 @@ -5001,16 +6603,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: @@ -5018,31 +6623,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)) @@ -5050,14 +6659,13 @@ internal static List CompleteMember(CompletionContext context, memberName = memberNameAst.Value + "*"; } } - else if (!(lastAst is ErrorExpressionAst) && targetExpr == null) + else if (lastAst is not ErrorExpressionAst && targetExpr == null) { // I don't think we can complete anything interesting 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) @@ -5077,10 +6685,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") @@ -5088,23 +6722,44 @@ 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 + { + targetExpr ??= parentAsMemberExpression.Expression; + } + } + else if (lastAst.Parent is BinaryExpressionAst binaryExpression && context.TokenAtCursor.Kind.Equals(TokenKind.Multiply)) + { + if (binaryExpression.Left is MemberExpressionAst memberExpression) { - var memberExprAst = (MemberExpressionAst)lastAst.Parent; - targetExpr = memberExprAst.Expression; + targetExpr = memberExpression.Expression; + if (memberExpression.Member is StringConstantExpressionAst stringExpression) + { + memberName = $"{stringExpression.Value}*"; + } } } - else if (lastAst.Parent is BinaryExpressionAst && context.TokenAtCursor.Kind.Equals(TokenKind.Multiply)) + else if (lastAst.Parent is ErrorStatementAst errorStatement) { - var memberExprAst = ((BinaryExpressionAst)lastAst.Parent).Left as MemberExpressionAst; - if (memberExprAst != null) + // Handles switches like: + // switch ($x) + // { + // 'RandomString'. + // { } + // } + Ast astBeforeMemberAccessToken = null; + for (int i = errorStatement.Bodies.Count - 1; i >= 0; i--) { - targetExpr = memberExprAst.Expression; - if (memberExprAst.Member is StringConstantExpressionAst) + astBeforeMemberAccessToken = errorStatement.Bodies[i]; + if (astBeforeMemberAccessToken.Extent.EndOffset < lastAst.Extent.EndOffset) { - memberName = ((StringConstantExpressionAst)memberExprAst.Member).Value + "*"; + break; } } + + if (astBeforeMemberAccessToken is ExpressionAst expression) + { + targetExpr = expression; + } } if (targetExpr == null) @@ -5138,6 +6793,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 @@ -5194,28 +6854,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)); + } + } } /// @@ -5245,23 +6954,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' @@ -5269,7 +7098,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); } } @@ -5281,14 +7110,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; @@ -5314,7 +7142,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; @@ -5333,21 +7161,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())); } @@ -5371,7 +7223,7 @@ private static string GetCimPropertyToString(CimPropertyDeclaration cimProperty) break; } - bool isReadOnly = (CimFlags.ReadOnly == (cimProperty.Flags & CimFlags.ReadOnly)); + bool isReadOnly = ((cimProperty.Flags & CimFlags.ReadOnly) == CimFlags.ReadOnly); return type + " " + cimProperty.Name + " { get; " + (isReadOnly ? "}" : "set; }"); } @@ -5389,6 +7241,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; } @@ -5444,6 +7302,7 @@ private static bool IsConstructor(object member) private abstract class TypeCompletionBase { internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix); + internal abstract CompletionResult GetCompletionResult(string keyMatched, string prefix, string suffix, string namespaceToRemove); internal static string RemoveBackTick(string typeName) @@ -5539,7 +7398,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 +7450,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 +7515,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 +7552,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 +7574,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 +7683,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 +7800,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 +7816,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 +7844,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 +7875,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 +7890,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 +7930,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.GetFiles(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 +7976,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 +8106,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 @@ -6460,8 +8588,7 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand var varValues = new List(); foreach (ExpressionAst nestedAst in expandableStringAst.NestedExpressions) { - var variableAst = nestedAst as VariableExpressionAst; - if (variableAst == null) { return false; } + if (nestedAst is not VariableExpressionAst variableAst) { return false; } string strValue = CombineVariableWithPartialPath(variableAst, null, executionContext); if (strValue != null) @@ -6484,66 +8611,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) @@ -6567,7 +8697,7 @@ internal static void CompleteMemberHelper( object value; if (SafeExprEvaluator.TrySafeEval(targetExpr, context.ExecutionContext, out value) && value != null) { - if (targetExpr is ArrayExpressionAst && !(value is object[])) + if (targetExpr is ArrayExpressionAst && value is not object[]) { // When the array contains only one element, the evaluation result would be that element. We wrap it into an array value = new[] { value }; @@ -6586,8 +8716,7 @@ internal static void CompleteMemberHelper( IEnumerable members; if (@static) { - var type = PSObject.Base(value) as Type; - if (type == null) + if (PSObject.Base(value) is not Type type) { return; } @@ -6612,7 +8741,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 + "'"; @@ -6653,14 +8782,13 @@ internal static void CompleteMemberHelper( var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); foreach (DictionaryEntry entry in dictionary) { - var key = entry.Key as string; - if (key == null) + if (entry.Key is not string key) continue; 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 + "'"; @@ -6718,32 +8846,6 @@ private static bool IsStaticTypeEnumerable(Type type) return false; } - private static bool CompletionRequiresQuotes(string completion, bool escape) - { - // If the tokenizer sees the completion as more than two tokens, or if there is some error, then - // some form of quoting is necessary (if it's a variable, we'd need ${}, filenames would need [], etc.) - - Language.Token[] tokens; - ParseError[] errors; - Language.Parser.ParseInput(completion, out tokens, out errors); - - char[] charToCheck = escape ? new[] { '$', '[', ']', '`' } : new[] { '$', '`' }; - - // Expect no errors and 2 tokens (1 is for our completion, the other is eof) - // Or if the completion is a keyword, we ignore the errors - bool requireQuote = !(errors.Length == 0 && tokens.Length == 2); - if ((!requireQuote && tokens[0] is StringToken) || - (tokens.Length == 2 && (tokens[0].TokenFlags & TokenFlags.Keyword) != 0)) - { - requireQuote = false; - var value = tokens[0].Text; - if (value.IndexOfAny(charToCheck) != -1) - requireQuote = true; - } - - return requireQuote; - } - private static bool ProviderSpecified(string path) { var index = path.IndexOf(':'); @@ -6810,7 +8912,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) { @@ -6845,7 +8947,7 @@ public int Compare(PSObject x, PSObject y) } } - private class CommandNameComparer : IComparer + private sealed class CommandNameComparer : IComparer { public int Compare(PSObject x, PSObject y) { @@ -6872,7 +8974,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. @@ -6963,6 +9065,7 @@ internal static bool TrySafeEval(ExpressionAst ast, ExecutionContext executionCo public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return false; } public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return false; } + // REVIEW: we could relax this to allow specific commands public object VisitCommand(CommandAst commandAst) { return false; } @@ -7127,7 +9230,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; @@ -7140,7 +9243,7 @@ IEnumerable IArgumentCompleter.CompleteArgument( CommandAst commandAst, IDictionary fakeBoundParameters) { - if (!(commandAst.Parent is PipelineAst pipelineAst)) + if (commandAst.Parent is not PipelineAst pipelineAst) { return null; } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs new file mode 100644 index 00000000000..edf31e8e97a --- /dev/null +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Buffers; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation +{ + /// + /// 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/CompletionResult.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs index 3fe4a54e023..0554a52ba17 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs @@ -65,22 +65,22 @@ public class CompletionResult /// /// Text to be used as the auto completion result. /// - private string _completionText; + private readonly string _completionText; /// /// Text to be displayed in a list. /// - private string _listItemText; + private readonly string _listItemText; /// /// The text for the tooltip with details to be displayed about the object. /// - private string _toolTip; + private readonly string _toolTip; /// /// Type of completion result. /// - private CompletionResultType _resultType; + private readonly CompletionResultType _resultType; /// /// Private member for null instance. @@ -168,26 +168,15 @@ internal static CompletionResult Null /// The text for the tooltip with details to be displayed about the object. public CompletionResult(string completionText, string listItemText, CompletionResultType resultType, string toolTip) { - if (string.IsNullOrEmpty(completionText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(completionText)); - } - - if (string.IsNullOrEmpty(listItemText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(listItemText)); - } + ArgumentException.ThrowIfNullOrEmpty(completionText); + ArgumentException.ThrowIfNullOrEmpty(listItemText); + ArgumentException.ThrowIfNullOrEmpty(toolTip); if (resultType < CompletionResultType.Text || resultType > CompletionResultType.DynamicKeyword) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(resultType), resultType); } - if (string.IsNullOrEmpty(toolTip)) - { - throw PSTraceSource.NewArgumentNullException(nameof(toolTip)); - } - _completionText = completionText; _listItemText = listItemText; _toolTip = toolTip; diff --git a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs index 9bb33ef56ba..c63a8e7f92d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs @@ -24,15 +24,15 @@ public class ArgumentCompleterAttribute : Attribute { /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods")] - public Type Type { get; private set; } + public Type Type { get; } /// - public ScriptBlock ScriptBlock { get; private set; } + public ScriptBlock ScriptBlock { get; } /// 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,24 +40,46 @@ 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; + } } /// /// A type specified by the must implement this interface. /// +#nullable enable public interface IArgumentCompleter { /// @@ -82,72 +104,178 @@ IEnumerable CompleteArgument( CommandAst commandAst, IDictionary fakeBoundParameters); } +#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")] public class RegisterArgumentCompleterCommand : PSCmdlet { + private const string PowerShellSetName = "PowerShellSet"; + private const string NativeCommandSetName = "NativeCommandSet"; + private const string NativeFallbackSetName = "NativeFallbackSet"; + + // Use a key that is unlikely to be a file name or path to indicate the fallback completer for native commands. + internal const string FallbackCompleterKey = "___ps::@@___"; + /// + /// Gets or sets the command names for which the argument completer is registered. /// - [Parameter(ParameterSetName = "NativeSet", Mandatory = true)] - [Parameter(ParameterSetName = "PowerShellSet")] + [Parameter(ParameterSetName = NativeCommandSetName, Mandatory = true)] + [Parameter(ParameterSetName = PowerShellSetName)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CommandName { get; set; } /// + /// Gets or sets the name of the parameter for which the argument completer is registered. /// - [Parameter(ParameterSetName = "PowerShellSet", Mandatory = true)] + [Parameter(ParameterSetName = PowerShellSetName, Mandatory = true)] public string ParameterName { get; set; } /// + /// Gets or sets the script block that will be executed to provide argument completions. /// [Parameter(Mandatory = true)] [AllowNull()] public ScriptBlock ScriptBlock { get; set; } /// + /// Indicates the argument completer is for native commands. /// - [Parameter(ParameterSetName = "NativeSet")] + [Parameter(ParameterSetName = NativeCommandSetName)] public SwitchParameter Native { get; set; } + /// + /// Indicates the argument completer is a fallback for any native commands that don't have a completer registered. + /// + [Parameter(ParameterSetName = NativeFallbackSetName)] + public SwitchParameter NativeFallback { get; set; } + /// /// protected override void EndProcessing() { Dictionary completerDictionary; - if (ParameterName != null) + + if (ParameterSetName is NativeFallbackSetName) { - completerDictionary = Context.CustomArgumentCompleters ?? - (Context.CustomArgumentCompleters = new Dictionary(StringComparer.OrdinalIgnoreCase)); + completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); + + SetKeyValue(completerDictionary, FallbackCompleterKey, ScriptBlock); } - else + else if (ParameterSetName is NativeCommandSetName) { - completerDictionary = Context.NativeArgumentCompleters ?? - (Context.NativeArgumentCompleters = new Dictionary(StringComparer.OrdinalIgnoreCase)); - } + completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); - if (CommandName == null || CommandName.Length == 0) + foreach (string command in CommandName) + { + var key = command?.Trim(); + if (string.IsNullOrEmpty(key)) + { + continue; + } + + SetKeyValue(completerDictionary, key, ScriptBlock); + } + } + else if (ParameterSetName is PowerShellSetName) { - CommandName = new[] { "" }; + completerDictionary = Context.CustomArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); + + string paramName = ParameterName.Trim(); + if (paramName.Length is 0) + { + return; + } + + if (CommandName is null || CommandName.Length is 0) + { + SetKeyValue(completerDictionary, paramName, ScriptBlock); + return; + } + + foreach (string command in CommandName) + { + var key = command?.Trim(); + key = string.IsNullOrEmpty(key) + ? paramName + : $"{key}:{paramName}"; + + SetKeyValue(completerDictionary, key, ScriptBlock); + } } - for (int i = 0; i < CommandName.Length; i++) + static void SetKeyValue(Dictionary table, string key, ScriptBlock value) { - var key = CommandName[i]; - if (!string.IsNullOrWhiteSpace(ParameterName)) + if (value is null) { - if (!string.IsNullOrWhiteSpace(key)) - { - key = key + ":" + ParameterName; - } - else - { - key = ParameterName; - } + table.Remove(key); + } + else + { + table[key] = value; } - - completerDictionary[key] = ScriptBlock; } } } @@ -164,7 +292,7 @@ protected override void EndProcessing() [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public class ArgumentCompletionsAttribute : Attribute { - private string[] _completions; + private readonly string[] _completions; /// /// Initializes a new instance of the ArgumentCompletionsAttribute class. diff --git a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs index d19a4b9bddb..2e7457cd812 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs @@ -201,9 +201,9 @@ internal AstPair(CommandParameterAst parameterAst, ExpressionAst argumentAst) ParameterArgumentType = AstParameterArgumentType.AstPair; ParameterSpecified = parameterAst != null; ArgumentSpecified = argumentAst != null; - ParameterName = parameterAst != null ? parameterAst.ParameterName : null; - ParameterText = parameterAst != null ? parameterAst.ParameterName : null; - ArgumentType = argumentAst != null ? argumentAst.StaticType : null; + ParameterName = parameterAst?.ParameterName; + ParameterText = parameterAst?.ParameterName; + ArgumentType = argumentAst?.StaticType; ParameterContainsArgument = false; Argument = argumentAst; @@ -261,8 +261,7 @@ public static class StaticParameterBinder /// The StaticBindingResult that represents the binding. public static StaticBindingResult BindCommand(CommandAst commandAst) { - bool resolve = true; - return BindCommand(commandAst, resolve); + return BindCommand(commandAst, resolve: true); } /// @@ -729,7 +728,10 @@ internal ParameterBindingResult() /// public object ConstantValue { - get { return _constantValue; } + get + { + return _constantValue; + } internal set { @@ -746,7 +748,10 @@ internal set /// public CommandElementAst Value { - get { return _value; } + get + { + return _value; + } internal set { @@ -782,12 +787,12 @@ internal StaticBindingError(CommandElementAst commandElement, ParameterBindingEx /// /// The command element associated with the exception. /// - public CommandElementAst CommandElement { get; private set; } + public CommandElementAst CommandElement { get; } /// /// The ParameterBindingException that this command element caused. /// - public ParameterBindingException BindingException { get; private set; } + public ParameterBindingException BindingException { get; } } #region "PseudoBindingInfo" @@ -944,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) { @@ -976,7 +982,7 @@ internal PseudoBindingInfo DoPseudoParameterBinding(CommandAst command, Type pip executionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - _bindingEffective = PrepareCommandElements(executionContext); + _bindingEffective = PrepareCommandElements(executionContext, paramAstAtCursor); } finally { @@ -1003,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) @@ -1151,12 +1160,12 @@ private void InitializeMembers() _bindableParameters = null; // reuse the collections/dictionaries - _arguments = _arguments ?? new Collection(); - _boundParameters = _boundParameters ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - _boundArguments = _boundArguments ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - _unboundParameters = _unboundParameters ?? new List(); - _boundPositionalParameter = _boundPositionalParameter ?? new Collection(); - _bindingExceptions = _bindingExceptions ?? new Dictionary(); + _arguments ??= new Collection(); + _boundParameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + _boundArguments ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + _unboundParameters ??= new List(); + _boundPositionalParameter ??= new Collection(); + _bindingExceptions ??= new Dictionary(); _arguments.Clear(); _boundParameters.Clear(); @@ -1171,16 +1180,16 @@ private void InitializeMembers() _isPipelineInputExpected = false; // reuse the collections - _parametersNotFound = _parametersNotFound ?? new Collection(); - _ambiguousParameters = _ambiguousParameters ?? new Collection(); - _duplicateParameters = _duplicateParameters ?? new Collection(); + _parametersNotFound ??= new Collection(); + _ambiguousParameters ??= new Collection(); + _duplicateParameters ??= new Collection(); _parametersNotFound.Clear(); _ambiguousParameters.Clear(); _duplicateParameters.Clear(); } - private bool PrepareCommandElements(ExecutionContext context) + private bool PrepareCommandElements(ExecutionContext context, CommandParameterAst paramAtCursor) { int commandIndex = 0; bool dotSource = _commandAst.InvocationOperator == TokenKind.Dot; @@ -1189,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) { @@ -1202,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 @@ -1226,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)); } } } @@ -1250,7 +1304,6 @@ private bool PrepareCommandElements(ExecutionContext context) _function = false; if (implementsDynamicParameters) { - ParameterBinderController.AddArgumentsToCommandProcessor(commandProcessor, argumentsToGetDynamicParameters.ToArray()); bool retryWithNoArgs = false, alreadyRetried = false; do @@ -1353,7 +1406,6 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string } ast.Visit(exportVisitor); - CommandProcessorBase commandProcessor = null; resolvedCommandName = _commandAst.GetCommandName(); @@ -1857,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 ef9cb56c636..9ca87f4c834 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -37,7 +37,7 @@ internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, _context = context; } - private ExecutionContext _context; + private readonly ExecutionContext _context; /// /// The name of the command we're looking for. @@ -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; } @@ -65,7 +65,10 @@ internal CommandLookupEventArgs(string commandName, CommandOrigin commandOrigin, /// public ScriptBlock CommandScriptBlock { - get { return _scriptBlock; } + get + { + return _scriptBlock; + } set { @@ -149,7 +152,7 @@ internal CommandDiscovery(ExecutionContext context) /// /// True if the cmdlet is a special cmdlet that shouldn't be part of the discovery list. Or false otherwise. /// - private bool IsSpecialCmdlet(Type implementingType) + private static bool IsSpecialCmdlet(Type implementingType) { // These commands should never be put in the discovery list. They are an internal implementation // detail of the formatting and output component. That component uses these cmdlets by creating @@ -259,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. + /// /// /// /// @@ -268,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; } @@ -283,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) @@ -298,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); @@ -313,113 +320,42 @@ internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, Execut } } - private static Collection GetPSSnapinNames(IEnumerable PSSnapins) + private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState, bool forCompletion = false) { - Collection result = new Collection(); + VerifyScriptRequirements(scriptInfo, Context, forCompletion); - foreach (var PSSnapin in PSSnapins) + if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) { - result.Add(BuildPSSnapInDisplayName(PSSnapin)); - } - - return result; - } - - private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState) - { - VerifyScriptRequirements(scriptInfo, Context); - - IEnumerable requiresPSSnapIns = scriptInfo.RequiresPSSnapIns; - if (requiresPSSnapIns != null && requiresPSSnapIns.Any()) - { - 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) @@ -428,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( @@ -461,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); @@ -488,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. /// @@ -526,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. /// /// @@ -536,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; @@ -582,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) { @@ -652,7 +565,7 @@ internal static void ShouldRun(ExecutionContext context, PSHost host, CommandInf private static CommandProcessorBase CreateCommandProcessorForScript(ScriptInfo scriptInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState) { - sessionState = sessionState ?? scriptInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; + sessionState ??= scriptInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; CommandProcessorBase scriptAsCmdletProcessor = GetScriptAsCmdletProcessor(scriptInfo, context, useNewScope, true, sessionState); if (scriptAsCmdletProcessor != null) { @@ -664,7 +577,7 @@ private static CommandProcessorBase CreateCommandProcessorForScript(ScriptInfo s private static CommandProcessorBase CreateCommandProcessorForScript(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState) { - sessionState = sessionState ?? scriptInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; + sessionState ??= scriptInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; CommandProcessorBase scriptAsCmdletProcessor = GetScriptAsCmdletProcessor(scriptInfo, context, useNewScope, true, sessionState); if (scriptAsCmdletProcessor != null) { @@ -676,7 +589,7 @@ private static CommandProcessorBase CreateCommandProcessorForScript(ExternalScri internal static CommandProcessorBase CreateCommandProcessorForScript(FunctionInfo functionInfo, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState) { - sessionState = sessionState ?? functionInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; + sessionState ??= functionInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; CommandProcessorBase scriptAsCmdletProcessor = GetScriptAsCmdletProcessor(functionInfo, context, useNewScope, false, sessionState); if (scriptAsCmdletProcessor != null) { @@ -688,7 +601,7 @@ internal static CommandProcessorBase CreateCommandProcessorForScript(FunctionInf internal static CommandProcessorBase CreateCommandProcessorForScript(ScriptBlock scriptblock, ExecutionContext context, bool useNewScope, SessionStateInternal sessionState) { - sessionState = sessionState ?? scriptblock.SessionStateInternal ?? context.EngineSessionState; + sessionState ??= scriptblock.SessionStateInternal ?? context.EngineSessionState; if (scriptblock.UsesCmdletBinding) { @@ -706,7 +619,7 @@ private static CommandProcessorBase GetScriptAsCmdletProcessor(IScriptCommandInf return null; } - sessionState = sessionState ?? scriptCommandInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; + sessionState ??= scriptCommandInfo.ScriptBlock.SessionStateInternal ?? context.EngineSessionState; return new CommandProcessor(scriptCommandInfo, context, useNewScope, fromScriptFile, sessionState); } @@ -784,7 +697,7 @@ internal static CommandInfo LookupCommandInfo( // Check the module auto-loading preference PSModuleAutoLoadingPreference moduleAutoLoadingPreference = GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference"); - if (eventArgs == null || eventArgs.StopSearch != true) + if (eventArgs == null || !eventArgs.StopSearch) { do { @@ -816,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 @@ -865,7 +775,7 @@ internal static CommandInfo LookupCommandInfo( if (result == null) { discoveryTracer.TraceError( - "'{0}' is not recognized as a cmdlet, function, operable program or script file.", + "'{0}' is not recognized as a cmdlet, function, executable program or script file.", commandName); CommandNotFoundException e = @@ -994,7 +904,7 @@ private static CommandInfo TryNormalSearch(string commandName, { if (!searcher.MoveNext()) { - if (!commandName.Contains("-") && !commandName.Contains("\\")) + if (!commandName.Contains('-') && !commandName.Contains('\\')) { discoveryTracer.WriteLine( "The command [{0}] was not found, trying again with get- prepended", @@ -1050,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(); @@ -1085,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); @@ -1139,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); @@ -1155,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 @@ -1166,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); @@ -1260,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) @@ -1277,14 +1179,13 @@ internal void UnregisterLookupCommandInfoAction(string currentAction, string com case "ActivePostCommand": currentActionSet = _activePostCommand; break; } - if (currentActionSet.Contains(command)) - currentActionSet.Remove(command); + currentActionSet.Remove(command); } - private HashSet _activePreLookup = new HashSet(StringComparer.OrdinalIgnoreCase); - private HashSet _activeModuleSearch = new HashSet(StringComparer.OrdinalIgnoreCase); - private HashSet _activeCommandNotFound = new HashSet(StringComparer.OrdinalIgnoreCase); - private HashSet _activePostCommand = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly HashSet _activePreLookup = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly HashSet _activeModuleSearch = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly HashSet _activeCommandNotFound = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly HashSet _activePostCommand = new HashSet(StringComparer.OrdinalIgnoreCase); /// /// Gets the resolved paths contained in the PATH environment @@ -1322,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) @@ -1330,11 +1231,17 @@ internal LookupPathCollection GetLookupDirectoryPaths() string tempDir = directory.TrimStart(); if (tempDir.EqualsOrdinalIgnoreCase("~")) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify); } else if (tempDir.StartsWith("~" + Path.DirectorySeparatorChar)) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar + tempDir.Substring(2); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify) + + Path.DirectorySeparatorChar + + tempDir.Substring(2); } _cachedPath.Add(tempDir); @@ -1348,7 +1255,7 @@ internal LookupPathCollection GetLookupDirectoryPaths() } // Cache the new lookup paths - return _cachedLookupPaths ?? (_cachedLookupPaths = result); + return _cachedLookupPaths ??= result; } /// @@ -1414,7 +1321,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; @@ -1428,7 +1335,7 @@ private static void InitPathExtCache(string pathExt) #region private members - private static object s_lockObject = new object(); + private static readonly object s_lockObject = new object(); private static string s_pathExtCacheKey; private static string[] s_cachedPathExtCollection; private static string[] s_cachedPathExtCollectionWithPs1; @@ -1486,7 +1393,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)) { @@ -1737,4 +1644,3 @@ internal class CommandDiscoveryEventSource : EventSource public void ModuleManifestAnalysisException(string ModulePath, string Exception) { WriteEvent(12, ModulePath, Exception); } } } - diff --git a/src/System.Management.Automation/engine/CommandInfo.cs b/src/System.Management.Automation/engine/CommandInfo.cs index 1090a3fbb86..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; @@ -230,7 +217,10 @@ public virtual Version Version /// internal ExecutionContext Context { - get { return _context; } + get + { + return _context; + } set { @@ -285,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; } @@ -464,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; } @@ -478,7 +462,7 @@ private MergedCommandParameterMetadata GetMergedCommandParameterMetadataSafely() return result; } - private class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs + private sealed class GetMergedCommandParameterMetadataSafelyEventArgs : EventArgs { public MergedCommandParameterMetadata Result; public ExceptionDispatchInfo Exception; @@ -526,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; @@ -583,7 +567,7 @@ public virtual Dictionary Parameters internal CommandMetadata ExternalCommandMetadata { - get { return _externalCommandMetadata ?? (_externalCommandMetadata = new CommandMetadata(this, true)); } + get { return _externalCommandMetadata ??= new CommandMetadata(this, true); } set { _externalCommandMetadata = value; } } @@ -889,7 +873,7 @@ public Type Type /// /// When a type is defined by PowerShell, the ast for that type. /// - public TypeDefinitionAst TypeDefinitionAst { get; private set; } + public TypeDefinitionAst TypeDefinitionAst { get; } private bool _typeWasCalculated; @@ -904,7 +888,7 @@ public override string ToString() } [DebuggerDisplay("{PSTypeName} {Name}")] - internal struct PSMemberNameAndType + internal readonly struct PSMemberNameAndType { public readonly string Name; @@ -925,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); @@ -936,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); } @@ -960,7 +944,7 @@ private PSSyntheticTypeName(string typeName, Type type, IList member.Name.Equals(nameof(PSTypeName), StringComparison.OrdinalIgnoreCase); + private static bool IsPSTypeName(in PSMemberNameAndType member) => member.Name.Equals(nameof(PSTypeName), StringComparison.OrdinalIgnoreCase); private static string GetMemberTypeProjection(string typename, IList members) { @@ -977,11 +961,11 @@ private static string GetMemberTypeProjection(string typename, IList m.Name)) + foreach (var m in members.OrderBy(static m => m.Name)) { if (!IsPSTypeName(m)) { - builder.Append(m.Name).Append(":"); + builder.Append(m.Name).Append(':'); } } @@ -992,6 +976,7 @@ private static string GetMemberTypeProjection(string typename, IList Members { get; } } +#nullable enable internal interface IScriptCommandInfo { ScriptBlock ScriptBlock { get; } diff --git a/src/System.Management.Automation/engine/CommandMetadata.cs b/src/System.Management.Automation/engine/CommandMetadata.cs index edcd9a9cd66..df767f714b0 100644 --- a/src/System.Management.Automation/engine/CommandMetadata.cs +++ b/src/System.Management.Automation/engine/CommandMetadata.cs @@ -464,7 +464,10 @@ internal CommandMetadata(ScriptBlock scriptblock, string commandName, ExecutionC /// public string DefaultParameterSetName { - get { return _defaultParameterSetName; } + get + { + return _defaultParameterSetName; + } set { @@ -528,7 +531,10 @@ public RemotingCapability RemotingCapability return _remotingCapability; } - set { _remotingCapability = value; } + set + { + _remotingCapability = value; + } } private RemotingCapability _remotingCapability = RemotingCapability.PowerShell; @@ -680,7 +686,7 @@ private void ConstructCmdletMetadataUsingReflection() // Determine if the cmdlet implements dynamic parameters by looking for the interface - Type dynamicParametersType = CommandType.GetInterface(typeof(IDynamicParameters).Name, true); + Type dynamicParametersType = CommandType.GetInterface(nameof(IDynamicParameters), true); if (dynamicParametersType != null) { @@ -841,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; } @@ -897,7 +898,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("DefaultParameterSetName='"); decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(_defaultParameterSetName)); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -909,7 +910,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("ConfirmImpact='"); decl.Append(ConfirmImpact); - decl.Append("'"); + decl.Append('\''); } if (SupportsPaging) @@ -926,7 +927,7 @@ internal string GetDecl() separator = ", "; } - if (PositionalBinding == false) + if (!PositionalBinding) { decl.Append(separator); decl.Append("PositionalBinding=$false"); @@ -938,7 +939,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("HelpUri='"); decl.Append(CodeGeneration.EscapeSingleQuotedStringContent(HelpUri)); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -947,7 +948,7 @@ internal string GetDecl() decl.Append(separator); decl.Append("RemotingCapability='"); decl.Append(_remotingCapability); - decl.Append("'"); + decl.Append('\''); separator = ", "; } @@ -1008,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)) @@ -1018,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; @@ -1057,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($_) @@ -1068,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) {{ @@ -1091,9 +1091,7 @@ internal string GetDynamicParamBlock() }} catch {{ throw }} -", - CodeGeneration.EscapeSingleQuotedStringContent(_wrappedCommand), - _wrappedCommandType); +"); } internal string GetEndBlock() @@ -1107,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 @@ -1222,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); @@ -1316,7 +1326,7 @@ public static Dictionary GetRestrictedCommands(SessionC List restrictedCommands = new List(); // all remoting cmdlets need to be included for workflow scenarios as wel - if (SessionCapabilities.RemoteServer == (sessionCapabilities & SessionCapabilities.RemoteServer)) + if ((sessionCapabilities & SessionCapabilities.RemoteServer) == SessionCapabilities.RemoteServer) { restrictedCommands.AddRange(GetRestrictedRemotingCommands()); } @@ -1533,7 +1543,7 @@ private static Collection GetRestrictedJobCommands() /// The command metadata cache. This is separate from the parameterMetadata cache /// because it is specific to cmdlets. /// - private static System.Collections.Concurrent.ConcurrentDictionary s_commandMetadataCache = + private static readonly System.Collections.Concurrent.ConcurrentDictionary s_commandMetadataCache = new System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); #endregion diff --git a/src/System.Management.Automation/engine/CommandParameter.cs b/src/System.Management.Automation/engine/CommandParameter.cs index 0ee5f7855f1..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; @@ -29,14 +29,17 @@ private class Argument private Parameter _parameter; private Argument _argument; private bool _spaceAfterParameter; + private bool _fromHashtableSplatting; - internal bool SpaceAfterParameter { get { return _spaceAfterParameter; } } + internal bool SpaceAfterParameter => _spaceAfterParameter; - internal bool ParameterNameSpecified { get { return _parameter != null; } } + internal bool ParameterNameSpecified => _parameter != null; - internal bool ArgumentSpecified { get { return _argument != null; } } + internal bool ArgumentSpecified => _argument != null; - internal bool ParameterAndArgumentSpecified { get { return ParameterNameSpecified && ArgumentSpecified; } } + internal bool ParameterAndArgumentSpecified => ParameterNameSpecified && ArgumentSpecified; + + internal bool FromHashtableSplatting => _fromHashtableSplatting; /// /// Gets and sets the string that represents parameter name, which does not include the '-' (dash). @@ -111,9 +114,9 @@ internal object ArgumentValue /// /// If an argument was specified and is to be splatted, returns true, otherwise false. /// - internal bool ArgumentSplatted + internal bool ArgumentToBeSplatted { - get { return _argument != null ? _argument.splatted : false; } + get { return _argument != null && _argument.splatted; } } /// @@ -121,10 +124,7 @@ internal bool ArgumentSplatted /// internal void SetArgumentValue(Ast ast, object value) { - if (_argument == null) - { - _argument = new Argument(); - } + _argument ??= new Argument(); _argument.value = value; _argument.ast = ast; @@ -201,19 +201,22 @@ internal static CommandParameterInternal CreateArgument( /// The ast of the argument value in the script. /// The argument value. /// Used in native commands to correctly handle -foo:bar vs. -foo: bar. + /// Indicate if this parameter-argument pair comes from splatting. internal static CommandParameterInternal CreateParameterWithArgument( Ast parameterAst, string parameterName, string parameterText, Ast argumentAst, object value, - bool spaceAfterParameter) + bool spaceAfterParameter, + bool fromSplatting = false) { return new CommandParameterInternal { _parameter = new Parameter { ast = parameterAst, parameterName = parameterName, parameterText = parameterText }, _argument = new Argument { ast = argumentAst, value = value }, - _spaceAfterParameter = spaceAfterParameter + _spaceAfterParameter = spaceAfterParameter, + _fromHashtableSplatting = fromSplatting, }; } diff --git a/src/System.Management.Automation/engine/CommandPathSearch.cs b/src/System.Management.Automation/engine/CommandPathSearch.cs index 96c6c8c1953..12c494ad8ea 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; @@ -278,9 +288,9 @@ public bool MoveNext() GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current); } - do // while lookupPathsEnumerator is valid + while (true) // while lookupPathsEnumerator is valid { - do // while patternEnumerator is valid + while (true) // while patternEnumerator is valid { // Try moving to the next path in the current results @@ -309,7 +319,7 @@ public bool MoveNext() } // Since we have reset the results, loop again to find the next result. - } while (true); + } if (result) { @@ -336,7 +346,7 @@ public bool MoveNext() } GetNewDirectoryResults(_patternEnumerator.Current, _lookupPathsEnumerator.Current); - } while (true); + } return result; } @@ -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); } } @@ -531,7 +542,7 @@ private void GetNewDirectoryResults(string pattern, string directory) /// The directory paths in which to look for commands. /// This is derived from the PATH environment variable. /// - private LookupPathCollection _lookupPaths; + private readonly LookupPathCollection _lookupPaths; /// /// The enumerator for the lookup paths. @@ -552,7 +563,7 @@ private void GetNewDirectoryResults(string pattern, string directory) /// /// The command name to search for. /// - private IEnumerable _patterns; + private readonly IEnumerable _patterns; /// /// The enumerator for the patterns. @@ -562,7 +573,7 @@ private void GetNewDirectoryResults(string pattern, string directory) /// /// A reference to the execution context for this runspace. /// - private ExecutionContext _context; + private readonly ExecutionContext _context; /// /// When reset is called, this gets set to true. Once MoveNext @@ -573,14 +584,13 @@ private void GetNewDirectoryResults(string pattern, string directory) /// /// If not null, called with the enumerated files for further processing. /// - private Func?> _postProcessEnumeratedFiles; + private readonly Func?> _postProcessEnumeratedFiles; - private string[] _orderedPathExt; - private Collection? _acceptableCommandNames; + private readonly string[] _orderedPathExt; + private readonly Collection? _acceptableCommandNames; - private 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 4433d75ca99..d1ef250d717 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -101,8 +101,7 @@ internal CommandProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext /// internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - Cmdlet cmdlet = command as Cmdlet; - if (cmdlet == null) + if (command is not Cmdlet cmdlet) { throw PSTraceSource.NewArgumentException(nameof(command)); } @@ -228,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; @@ -310,16 +310,14 @@ 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 PSScriptCmdlet)) + if (Context._debuggingMode > 0 && Command is not PSScriptCmdlet) { Context.Debugger.CheckCommand(this.Command.MyInvocation); } @@ -327,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); } } @@ -367,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) @@ -401,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 @@ -528,7 +523,7 @@ internal sealed override bool Read() try { // Process the input pipeline object - if (false == ProcessInputPipelineObject(inputObject)) + if (!ProcessInputPipelineObject(inputObject)) { // The input object was not bound to any parameters of the cmdlet. // Write a non-terminating error and continue with the next input @@ -608,7 +603,7 @@ private void WriteInputObjectError( string errorId, params object[] args) { - Type inputObjectType = (inputObject == null) ? null : inputObject.GetType(); + Type inputObjectType = inputObject?.GetType(); ParameterBindingException bindingException = new ParameterBindingException( ErrorCategory.InvalidArgument, @@ -692,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. /// /// @@ -785,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); } } @@ -825,7 +820,7 @@ private void InitCommon() /// /// Help target to request. /// Help category to request. - /// true if user requested help; false otherwise. + /// if user requested help; otherwise. internal override bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory) { if (this.arguments != null) @@ -870,4 +865,3 @@ internal override bool IsHelpRequested(out string helpTarget, out HelpCategory h #endregion helper_methods } } - diff --git a/src/System.Management.Automation/engine/CommandProcessorBase.cs b/src/System.Management.Automation/engine/CommandProcessorBase.cs index 87fd4606c9f..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. @@ -121,7 +128,10 @@ internal bool AddedToPipelineAlready /// internal InternalCommand Command { - get { return _command; } + get + { + return _command; + } set { @@ -149,6 +159,7 @@ internal virtual ObsoleteAttribute ObsoleteAttribute { get { return null; } } + // Full Qualified ID for the obsolete command warning private const string FQIDCommandObsolete = "CommandObsolete"; @@ -183,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. @@ -196,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. @@ -215,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); } } } @@ -256,7 +278,7 @@ internal ExecutionContext Context /// /// Help target to request. /// Help category to request. - /// true if user requested help; false otherwise. + /// if user requested help; otherwise. internal virtual bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory) { // by default we don't handle "-?" parameter at all @@ -341,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; @@ -367,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; @@ -448,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 { @@ -504,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 PSScriptCmdlet)) - { - Context.Debugger.CheckCommand(this.Command.MyInvocation); - } - - Command.DoBeginProcessing(); + Context.Debugger.CheckCommand(Command.MyInvocation); } + + Command.DoBeginProcessing(); } } catch (Exception e) @@ -585,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); } } @@ -627,44 +632,119 @@ 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); + } + } - // Restore the previous session state - if (_previousCommandSessionState != null) + 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; + + 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; } /// @@ -773,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 @@ -809,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 @@ -896,7 +967,7 @@ internal void ManageScriptException(RuntimeException e) // An explicit throw is written to $error as an ErrorRecord, so we // skip adding what is more or less a duplicate. - if (!(e is PipelineStoppedException) && !e.WasThrownFromThrowStatement) + if (e is not PipelineStoppedException && !e.WasThrownFromThrowStatement) commandRuntime.AppendErrorToVariables(e); } // Upstream cmdlets see only that execution stopped @@ -939,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(); } @@ -956,14 +1039,6 @@ private void Dispose(bool disposing) _disposed = true; } - /// - /// Finalizer for class CommandProcessorBase. - /// - ~CommandProcessorBase() - { - Dispose(false); - } - #endregion IDispose } } diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs index d3a1033c932..638ccd4ac79 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); } @@ -842,18 +833,28 @@ private static bool checkPath(string path, string commandName) // Don't return commands to the user if that might result in: // - Trusted commands calling untrusted functions that the user has overridden // - Debug prompts calling internal functions that are likely to have code injection - private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? result, ExecutionContext executionContext) + private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? result, ExecutionContext executionContext) { if (result == null) { 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 bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu } } - if (module == null) - { - module = modules[0]; - } + module ??= modules[0]; } return module; @@ -953,24 +951,13 @@ private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu if (result != null) { - if (result is FilterInfo) - { - CommandDiscovery.discoveryTracer.WriteLine( - "Filter found: {0}", - function); - } - else if (result is ConfigurationInfo) - { - CommandDiscovery.discoveryTracer.WriteLine( - "Configuration found: {0}", - function); - } - else + var formatString = result switch { - 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 bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu { 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)) { @@ -1087,7 +1072,7 @@ private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu string? result = null; if (_context.EngineSessionState != null && - _context.EngineSessionState.ProviderCount > 0) + _context.EngineSessionState.ProviderCount > 0 && _commandName.Length != 0) { // NTRAID#Windows OS Bugs-1009294-2004/02/04-JeffJon // This is really slow. Maybe since we are only allowing FS paths right @@ -1099,7 +1084,9 @@ private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu // Relative Path: ".\command.exe" // Home Path: "~\command.exe" // Drive Relative Path: "\Users\User\AppData\Local\Temp\command.exe" - if (_commandName[0] == '.' || _commandName[0] == '~' || _commandName[0] == '\\') + + char firstChar = _commandName[0]; + if (firstChar == '.' || firstChar == '~' || firstChar == '\\') { using (CommandDiscovery.discoveryTracer.TraceScope( "{0} appears to be a relative path. Trying to resolve relative path", @@ -1334,10 +1321,6 @@ private bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInfo? resu /// internal LookupPathCollection ConstructSearchPatternsFromName(string name, bool commandDiscovery = false) { - Dbg.Assert( - !string.IsNullOrEmpty(name), - "Caller should verify name"); - var result = new LookupPathCollection(); // First check to see if the commandName has an extension, if so @@ -1456,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; @@ -1465,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; @@ -1501,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... /// @@ -1533,7 +1521,7 @@ private void setupPathSearcher() _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, acceptableCommandNames: null, - useFuzzyMatch: _commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch)); + _fuzzyMatcher); } else { @@ -1549,7 +1537,7 @@ private void setupPathSearcher() _context.CommandDiscovery.GetLookupDirectoryPaths(), _context, ConstructSearchPatternsFromName(_commandName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else if (_canDoPathLookupResult == CanDoPathLookupResult.PathIsRooted) { @@ -1573,7 +1561,7 @@ private void setupPathSearcher() directoryCollection, _context, ConstructSearchPatternsFromName(fileName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else { @@ -1613,7 +1601,7 @@ private void setupPathSearcher() directoryCollection, _context, ConstructSearchPatternsFromName(fileName, commandDiscovery: true), - useFuzzyMatch: false); + fuzzyMatcher: null); } else { @@ -1732,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 9cc0b2ad899..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 { @@ -234,7 +255,7 @@ public string PipelineVariable #endregion parameters - private MshCommandRuntime _commandRuntime; + private readonly MshCommandRuntime _commandRuntime; internal class ValidateVariableName : ValidateArgumentsAttribute { @@ -261,4 +282,3 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin } } } - diff --git a/src/System.Management.Automation/engine/CompiledCommandParameter.cs b/src/System.Management.Automation/engine/CompiledCommandParameter.cs index ba4fa1fbb21..d2ca1aedd91 100644 --- a/src/System.Management.Automation/engine/CompiledCommandParameter.cs +++ b/src/System.Management.Automation/engine/CompiledCommandParameter.cs @@ -69,7 +69,7 @@ internal CompiledCommandParameter(RuntimeDefinedParameter runtimeDefinedParamete } } - if (!(attribute is ArgumentTypeConverterAttribute)) + if (attribute is not ArgumentTypeConverterAttribute) { ProcessAttribute(runtimeDefinedParameter.Name, attribute, ref validationAttributes, ref argTransformationAttributes, ref aliases); } @@ -193,7 +193,7 @@ internal CompiledCommandParameter(MemberInfo member, bool processingDynamicParam /// /// Gets the name of the parameter. /// - internal string Name { get; private set; } + internal string Name { get; } /// /// The PSTypeName from a PSTypeNameAttribute. @@ -203,38 +203,38 @@ internal CompiledCommandParameter(MemberInfo member, bool processingDynamicParam /// /// Gets the Type information of the attribute. /// - internal Type Type { get; private set; } + internal Type Type { get; } /// /// Gets the Type information of the attribute. /// - internal Type DeclaringType { get; private set; } + internal Type DeclaringType { get; } /// /// Gets whether the parameter is a dynamic parameter or not. /// - internal bool IsDynamic { get; private set; } + internal bool IsDynamic { get; } /// /// Gets the parameter collection type information. /// - internal ParameterCollectionTypeInformation CollectionTypeInformation { get; private set; } + internal ParameterCollectionTypeInformation CollectionTypeInformation { get; } /// /// A collection of the attributes found on the member. The attributes have been compiled into /// a format that easier to digest by the metadata processor. /// - internal Collection CompiledAttributes { get; private set; } + internal Collection CompiledAttributes { get; } /// /// Gets the collection of data generation attributes on this parameter. /// - internal ArgumentTransformationAttribute[] ArgumentTransformationAttributes { get; private set; } + internal ArgumentTransformationAttribute[] ArgumentTransformationAttributes { get; } /// /// Gets the collection of data validation attributes on this parameter. /// - internal ValidateArgumentsAttribute[] ValidationAttributes { get; private set; } + internal ValidateArgumentsAttribute[] ValidationAttributes { get; } /// /// Get and private set the obsolete attribute on this parameter. @@ -299,12 +299,12 @@ internal CompiledCommandParameter(MemberInfo member, bool processingDynamicParam /// /// A dictionary of the parameter sets and the parameter set specific data for this parameter. /// - internal Dictionary ParameterSetData { get; private set; } + internal Dictionary ParameterSetData { get; } /// /// The alias names for this parameter. /// - internal string[] Aliases { get; private set; } + internal string[] Aliases { get; } /// /// Determines if this parameter takes pipeline input for any of the specified @@ -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; } @@ -625,7 +623,7 @@ internal ParameterCollectionTypeInformation(Type type) return; } - bool implementsIList = (type.GetInterface(typeof(IList).Name) != null); + bool implementsIList = (type.GetInterface(nameof(IList)) != null); // Look for class Collection. Collection implements IList, and also IList // is more efficient to bind than ICollection. This optimization @@ -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 @@ -675,11 +673,11 @@ internal ParameterCollectionTypeInformation(Type type) /// /// The collection type of the parameter. /// - internal ParameterCollectionType ParameterCollectionType { get; private set; } + internal ParameterCollectionType ParameterCollectionType { get; } /// /// The type of the elements in the collection. /// - internal Type ElementType { get; private set; } + internal Type ElementType { get; } } } diff --git a/src/System.Management.Automation/engine/ContentCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/ContentCmdletProviderInterfaces.cs index 382f6b7e46a..a5ede5c7c97 100644 --- a/src/System.Management.Automation/engine/ContentCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ContentCmdletProviderInterfaces.cs @@ -586,10 +586,9 @@ internal object ClearContentDynamicParameters(string path, CmdletProviderContext #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index b6752387d58..136083d04d7 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 == false) && - (methods[0].isGeneric == false) && - (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) @@ -1658,8 +1723,7 @@ internal static void SetReferences(object[] arguments, MethodInformation methodI // It still might be an PSObject wrapping an PSReference if (originalArgumentReference == null) { - PSObject originalArgumentObj = originalArgument as PSObject; - if (originalArgumentObj == null) + if (originalArgument is not PSObject originalArgumentObj) { continue; } @@ -1762,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) @@ -1814,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. @@ -2014,7 +2078,7 @@ internal class CacheTable /// . /// internal Collection memberCollection; - private Dictionary _indexes; + private readonly Dictionary _indexes; internal CacheTable() { @@ -2166,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) { @@ -2203,10 +2267,7 @@ internal object Invoke(object target, object[] arguments) if (!_useReflection) { - if (_methodInvoker == null) - { - _methodInvoker = GetMethodInvoker(methodInfo); - } + _methodInvoker ??= GetMethodInvoker(methodInfo); if (_methodInvoker != null) { @@ -2217,7 +2278,7 @@ internal object Invoke(object target, object[] arguments) return method.Invoke(target, arguments); } - private static OpCode[] s_ldc = new OpCode[] { + private static readonly OpCode[] s_ldc = new OpCode[] { OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 }; @@ -2562,7 +2623,7 @@ internal class DotNetAdapter : Adapter private const BindingFlags staticBindingFlags = (BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Static); - private bool _isStatic; + private readonly bool _isStatic; internal DotNetAdapter() { } @@ -2575,25 +2636,25 @@ internal DotNetAdapter(bool isStatic) /// /// CLR reflection property cache for instance properties. /// - private static Dictionary s_instancePropertyCacheTable = new Dictionary(); + private static readonly Dictionary s_instancePropertyCacheTable = new Dictionary(); // This static is thread safe based on the lock in GetStaticPropertyReflectionTable /// /// CLR reflection property cache for static properties. /// - private static Dictionary s_staticPropertyCacheTable = new Dictionary(); + private static readonly Dictionary s_staticPropertyCacheTable = new Dictionary(); // This static is thread safe based on the lock in GetInstanceMethodReflectionTable /// /// CLR reflection method cache for instance methods. /// - private static Dictionary s_instanceMethodCacheTable = new Dictionary(); + private static readonly Dictionary s_instanceMethodCacheTable = new Dictionary(); // This static is thread safe based on the lock in GetStaticMethodReflectionTable /// /// CLR reflection method cache for static methods. /// - private static Dictionary s_staticMethodCacheTable = new Dictionary(); + private static readonly Dictionary s_staticMethodCacheTable = new Dictionary(); // This static is thread safe based on the lock in GetInstanceMethodReflectionTable /// @@ -2617,7 +2678,7 @@ internal class MethodCacheEntry : CacheEntry /// internal Func PSMethodCtor; - internal MethodCacheEntry(MethodBase[] methods) + internal MethodCacheEntry(IList methods) { methodInformationStructures = DotNetAdapter.GetMethodInformationArray(methods); } @@ -2722,7 +2783,7 @@ internal ParameterizedPropertyCacheEntry(List properties) definition.Append(" {"); definition.Append(extraDefinition); - definition.Append("}"); + definition.Append('}'); definitionArray.Add(definition.ToString()); } @@ -2748,6 +2809,7 @@ internal ParameterizedPropertyCacheEntry(List properties) internal class PropertyCacheEntry : CacheEntry { internal delegate object GetterDelegate(object instance); + internal delegate void SetterDelegate(object instance, object setValue); internal PropertyCacheEntry(PropertyInfo property) @@ -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 @@ -3878,8 +3860,7 @@ internal void AddAllDynamicMembers(object obj, PSMemberInfoInternalCollection private static bool PropertyIsStatic(PSProperty property) { - PropertyCacheEntry entry = property.adapterData as PropertyCacheEntry; - if (entry == null) + if (property.adapterData is not PropertyCacheEntry entry) { return false; } @@ -3887,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 @@ -3895,7 +3903,7 @@ private static bool PropertyIsStatic(PSProperty property) internal override bool CanSiteBinderOptimize(MemberTypes typeToOperateOn) { return true; } - private static ConcurrentDictionary s_typeToTypeNameDictionary = + private static readonly ConcurrentDictionary s_typeToTypeNameDictionary = new ConcurrentDictionary(); internal static ConsolidatedString GetInternedTypeNameHierarchy(Type type) @@ -3912,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. /// @@ -3949,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) @@ -3995,7 +4003,7 @@ protected override string PropertyToString(PSProperty property) } returnValue.Append(PropertyType(property, forDisplay: true)); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(property.Name); returnValue.Append(" {"); if (PropertyIsGettable(property)) @@ -4008,7 +4016,7 @@ protected override string PropertyToString(PSProperty property) returnValue.Append("set;"); } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -4285,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); } @@ -4358,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. @@ -4415,7 +4422,7 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method if (method != null) { builder.Append(ToStringCodeMethods.Type(method.ReturnType)); - builder.Append(" "); + builder.Append(' '); } else { @@ -4423,20 +4430,20 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method if (ctorInfo != null) { builder.Append(ToStringCodeMethods.Type(ctorInfo.DeclaringType)); - builder.Append(" "); + builder.Append(' '); } } if (methodEntry.DeclaringType.IsInterface) { builder.Append(ToStringCodeMethods.Type(methodEntry.DeclaringType, dropNamespaces: true)); - builder.Append("."); + builder.Append('.'); } builder.Append(memberName ?? methodEntry.Name); - if (methodEntry.IsGenericMethodDefinition) + if (methodEntry.IsGenericMethodDefinition || methodEntry.IsGenericMethod) { - builder.Append("["); + builder.Append('['); Type[] genericArgs = methodEntry.GetGenericArguments(); for (int i = 0; i < genericArgs.Length; i++) @@ -4446,10 +4453,10 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method builder.Append(ToStringCodeMethods.Type(genericArgs[i])); } - builder.Append("]"); + builder.Append(']'); } - builder.Append("("); + builder.Append('('); System.Reflection.ParameterInfo[] parameters = methodEntry.GetParameters(); int parametersLength = parameters.Length - parametersToIgnore; if (parametersLength > 0) @@ -4478,15 +4485,22 @@ internal static string GetMethodInfoOverloadDefinition(string memberName, Method } builder.Append(ToStringCodeMethods.Type(parameterType)); - builder.Append(" "); + builder.Append(' '); builder.Append(parameter.Name); + + if (parameter.HasDefaultValue) + { + builder.Append(" = "); + builder.Append(GetDefaultValueStringRepresentation(parameter)); + } + builder.Append(", "); } builder.Remove(builder.Length - 2, 2); } - builder.Append(")"); + builder.Append(')'); return builder.ToString(); } @@ -4534,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); @@ -4751,12 +4765,13 @@ 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. /// internal class DotNetAdapterWithComTypeName : DotNetAdapter { - private ComTypeInfo _comTypeInfo; + private readonly ComTypeInfo _comTypeInfo; internal DotNetAdapterWithComTypeName(ComTypeInfo comTypeInfo) { @@ -4781,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. @@ -5256,9 +5273,9 @@ protected override IEnumerable GetTypeNameHierarchy(object obj) if (firstType == null) { firstType = new StringBuilder(baseType); - firstType.Append("#"); + firstType.Append('#'); firstType.Append(node.NamespaceURI); - firstType.Append("#"); + firstType.Append('#'); firstType.Append(node.LocalName); yield return firstType.ToString(); } @@ -5423,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; @@ -5904,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); @@ -5941,11 +5958,11 @@ private static MethodInfo Infer(MethodInfo genericMethod, ICollection type using (s_tracer.TraceScope("Inferring type parameters for the following method: {0}", genericMethod)) { - if (PSTraceSourceOptions.WriteLine == (s_tracer.Options & PSTraceSourceOptions.WriteLine)) + if ((s_tracer.Options & PSTraceSourceOptions.WriteLine) == PSTraceSourceOptions.WriteLine) { 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); @@ -5955,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; } @@ -5963,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) @@ -5991,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(), @@ -6023,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); @@ -6034,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 6b9c4be8d4d..c921b9a084d 100644 --- a/src/System.Management.Automation/engine/Credential.cs +++ b/src/System.Management.Automation/engine/Credential.cs @@ -10,13 +10,10 @@ using System.Security.Cryptography; using Microsoft.PowerShell; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "Credential.resources", MessageId = "Cred")] - namespace System.Management.Automation { /// - /// 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 +82,7 @@ public enum PSCredentialUIOptions /// Offers a centralized way to manage usernames, passwords, and /// credentials. /// - [Serializable()] + [Serializable] public sealed class PSCredential : ISerializable { /// @@ -177,8 +174,8 @@ private PSCredential(SerializationInfo info, StreamingContext context) } } - private string _userName; - private SecureString _password; + private readonly string _userName; + private readonly SecureString _password; /// /// User's name. @@ -405,4 +402,3 @@ private static void SplitUserDomain(string input, } } } - diff --git a/src/System.Management.Automation/engine/DataStoreAdapter.cs b/src/System.Management.Automation/engine/DataStoreAdapter.cs index c7db11bf081..07c7cef4d01 100644 --- a/src/System.Management.Automation/engine/DataStoreAdapter.cs +++ b/src/System.Management.Automation/engine/DataStoreAdapter.cs @@ -26,10 +26,10 @@ 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 Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("PSDriveInfo", "The namespace navigation 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. /// /// @@ -430,7 +430,7 @@ public PSDriveInfo( /// If null, the current user credential is used. /// /// - /// It indicates if the the created PSDrive would be + /// It indicates if the created PSDrive would be /// persisted across PowerShell sessions. /// /// @@ -830,4 +830,3 @@ internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) } } } - diff --git a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs index f1e798c0edf..3ea2fd00fff 100644 --- a/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs +++ b/src/System.Management.Automation/engine/DataStoreAdapterProvider.cs @@ -34,7 +34,7 @@ public class ProviderInfo /// /// The instance of session state the provider belongs to. /// - private SessionState _sessionState; + private readonly SessionState _sessionState; private string _fullName; private string _cachedModuleName; @@ -51,7 +51,7 @@ internal string FullName { get { - string GetFullName(string name, string psSnapInName, string moduleName) + static string GetFullName(string name, string psSnapInName, string moduleName) { string result = name; if (!string.IsNullOrEmpty(psSnapInName)) @@ -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; } @@ -221,7 +221,7 @@ public Collection Drives /// A hidden drive for the provider that is used for setting /// the location to a provider-qualified path. /// - private PSDriveInfo _hiddenDrive; + private readonly PSDriveInfo _hiddenDrive; /// /// Gets the hidden drive for the provider that is used @@ -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. @@ -686,4 +686,3 @@ internal PSNoteProperty GetNotePropertyForProviderCmdlets(string name) } } } - diff --git a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs index 95e7572810f..746d809d831 100644 --- a/src/System.Management.Automation/engine/DefaultCommandRuntime.cs +++ b/src/System.Management.Automation/engine/DefaultCommandRuntime.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #pragma warning disable 1634, 1691 using System.Collections; @@ -13,15 +14,14 @@ namespace System.Management.Automation /// internal class DefaultCommandRuntime : ICommandRuntime2 { - private List _output; + private readonly List _output; /// /// Constructs an instance of the default ICommandRuntime object /// that will write objects into the list that was passed. /// public DefaultCommandRuntime(List outputList) { - if (outputList == null) - throw new System.ArgumentNullException(nameof(outputList)); + ArgumentNullException.ThrowIfNull(outputList); _output = outputList; } @@ -29,7 +29,7 @@ public DefaultCommandRuntime(List outputList) /// /// Return the instance of PSHost - null by default. /// - public PSHost Host { set; get; } + public PSHost Host { get; set; } #region Write /// @@ -64,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 @@ -229,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 36bfc73c573..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. @@ -387,9 +387,8 @@ public Collection GetAllForProvider(string providerName) #region private data // A private reference to the internal session state of the engine. - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/DscResourceInfo.cs b/src/System.Management.Automation/engine/DscResourceInfo.cs index 93f7be37323..12e81f5becf 100644 --- a/src/System.Management.Automation/engine/DscResourceInfo.cs +++ b/src/System.Management.Automation/engine/DscResourceInfo.cs @@ -57,7 +57,7 @@ internal DscResourceInfo(string name, string friendlyName, string path, string p /// /// Name of the DSC Resource. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets or sets resource type name. diff --git a/src/System.Management.Automation/engine/DscResourceSearcher.cs b/src/System.Management.Automation/engine/DscResourceSearcher.cs index 8bb4f5bd267..bfd406f56ff 100644 --- a/src/System.Management.Automation/engine/DscResourceSearcher.cs +++ b/src/System.Management.Automation/engine/DscResourceSearcher.cs @@ -27,8 +27,8 @@ internal DscResourceSearcher( #region private properties - private string _resourceName = null; - private ExecutionContext _context = null; + private readonly string _resourceName = null; + private readonly ExecutionContext _context = null; private DscResourceInfo _currentMatch = null; private IEnumerator _matchingResource = null; private Collection _matchingResourceList = null; diff --git a/src/System.Management.Automation/engine/EngineIntrinsics.cs b/src/System.Management.Automation/engine/EngineIntrinsics.cs index d0bc7a2f100..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; @@ -101,17 +98,16 @@ public SessionState SessionState /// public CommandInvocationIntrinsics InvokeCommand { - get { return _invokeCommand ?? (_invokeCommand = new CommandInvocationIntrinsics(_context)); } + get { return _invokeCommand ??= new CommandInvocationIntrinsics(_context); } } #endregion Public methods #region private data - private ExecutionContext _context; - private PSHost _host; + private readonly ExecutionContext _context; + private readonly PSHost _host; private CommandInvocationIntrinsics _invokeCommand; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs b/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs index 88aa49e98d2..056372ac63e 100644 --- a/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs +++ b/src/System.Management.Automation/engine/EnumExpressionEvaluator.cs @@ -157,6 +157,7 @@ internal abstract class Node public Node Operand1 { get; set; } internal abstract bool Eval(object val); + internal abstract bool ExistEnum(object enumVal); } @@ -308,7 +309,7 @@ internal override bool ExistEnum(object enumVal) return exist; } - private bool isUnsigned(Type type) + private static bool isUnsigned(Type type) { return (type == typeof(ulong) || type == typeof(uint) || type == typeof(ushort) || type == typeof(byte)); } @@ -318,7 +319,7 @@ private bool isUnsigned(Type type) #region private members - private Type _underType = null; + private readonly Type _underType = null; #endregion @@ -385,7 +386,7 @@ internal bool ExistsInExpression(T flagName) /// /// A generic list of tokenized input. /// - private List TokenizeInput(string input) + private static List TokenizeInput(string input) { List tokenList = new List(); int _offset = 0; @@ -411,7 +412,7 @@ private List TokenizeInput(string input) /// /// Current offset position for the string parser. /// - private void FindNextToken(string input, ref int _offset) + private static void FindNextToken(string input, ref int _offset) { while (_offset < input.Length) { @@ -438,7 +439,7 @@ private void FindNextToken(string input, ref int _offset) /// /// The next token on the input string /// - private Token GetNextToken(string input, ref int _offset) + private static Token GetNextToken(string input, ref int _offset) { StringBuilder sb = new StringBuilder(); // bool singleQuoted = false; @@ -521,7 +522,7 @@ private Token GetNextToken(string input, ref int _offset) /// /// A list of tokenized input. /// - private void CheckSyntaxError(List tokenList) + private static void CheckSyntaxError(List tokenList) { // Initialize, assuming preceded by OR TokenKind previous = TokenKind.Or; @@ -576,7 +577,7 @@ private void CheckSyntaxError(List tokenList) /// /// Tokenized list of the input string. /// - private Node ConstructExpressionTree(List tokenList) + private static Node ConstructExpressionTree(List tokenList) { bool notFlag = false; Queue andQueue = new Queue(); diff --git a/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs b/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs index 543d920418f..5c166c69a63 100644 --- a/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs +++ b/src/System.Management.Automation/engine/EnumMinimumDisambiguation.cs @@ -85,7 +85,7 @@ internal static string EnumDisambiguate(string text, Type enumType) } // No special cases match, throw error for multiple matches. StringBuilder matchListSB = new StringBuilder(namesWithMatchingPrefix[0]); - string separator = ", "; + const string separator = ", "; for (int i = 1; i < namesWithMatchingPrefix.Count; i++) { matchListSB.Append(separator); @@ -106,7 +106,7 @@ internal static string EnumDisambiguate(string text, Type enumType) internal static string EnumAllValues(Type enumType) { string[] names = Enum.GetNames(enumType); - string separator = ", "; + const string separator = ", "; StringBuilder returnValue = new StringBuilder(); if (names.Length != 0) { @@ -122,6 +122,6 @@ internal static string EnumAllValues(Type enumType) return returnValue.ToString(); } - private static Dictionary s_specialDisambiguateCases = new Dictionary(); + private static readonly Dictionary s_specialDisambiguateCases = new Dictionary(); } } diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index fc346e2ee96..6dfaf34fbec 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; } @@ -477,7 +478,7 @@ public override string ToString() #region Private // back-reference for facade class - private ErrorRecord _errorRecord; + private readonly ErrorRecord _errorRecord; /// /// The Activity, Reason, TargetName and TargetType strings in @@ -495,7 +496,7 @@ public override string ToString() /// internal static string Ellipsize(CultureInfo uiCultureInfo, string original) { - if (40 >= original.Length) + if (original.Length <= 40) { return original; } @@ -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 @@ -562,7 +562,7 @@ public ErrorDetails(string message) /// /// /// - /// + /// /// insertion parameters /// /// @@ -584,7 +584,7 @@ public ErrorDetails(string message) /// by overriding virtual method /// . /// This constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( Cmdlet cmdlet, @@ -610,7 +610,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -637,7 +637,7 @@ public ErrorDetails( /// will implement /// . /// The constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( IResourceSupplier resourceSupplier, @@ -663,7 +663,7 @@ public ErrorDetails( /// /// /// - /// + /// /// insertion parameters /// /// @@ -678,7 +678,7 @@ public ErrorDetails( /// This constructor first loads a template string from the assembly using /// . /// The constructor then inserts the specified args using - /// . + /// . /// public ErrorDetails( System.Reflection.Assembly assembly, @@ -720,7 +720,6 @@ protected ErrorDetails(SerializationInfo info, /// /// Serialization information. /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info != null) @@ -757,7 +756,7 @@ public string Message get { return ErrorRecord.NotNull(_message); } } - private string _message = string.Empty; + private readonly string _message = string.Empty; /// /// Text describing the recommended action in the event that this error @@ -770,7 +769,10 @@ public string Message /// public string RecommendedAction { - get { return ErrorRecord.NotNull(_recommendedAction); } + get + { + return ErrorRecord.NotNull(_recommendedAction); + } set { @@ -794,7 +796,7 @@ internal Exception TextLookupError #region ToString /// - /// As + /// As /// /// Developer-readable identifier. public override string ToString() @@ -983,7 +985,6 @@ private string BuildMessage( /// . /// rather than the actual exception, to avoid the mutual references. /// - [Serializable] public class ErrorRecord : ISerializable { #region Constructor @@ -1024,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; @@ -1077,7 +1075,6 @@ protected ErrorRecord(SerializationInfo info, /// /// Serialization information. /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] public virtual void GetObjectData(SerializationInfo info, StreamingContext context) { if (info != null) @@ -1206,21 +1203,21 @@ internal void ToPSObjectForRemoting(PSObject dest) private void ToPSObjectForRemoting(PSObject dest, bool serializeExtInfo) { - RemotingEncoder.AddNoteProperty(dest, "Exception", delegate () { return Exception; }); - RemotingEncoder.AddNoteProperty(dest, "TargetObject", delegate () { return TargetObject; }); - RemotingEncoder.AddNoteProperty(dest, "FullyQualifiedErrorId", delegate () { return FullyQualifiedErrorId; }); - RemotingEncoder.AddNoteProperty(dest, "InvocationInfo", delegate () { return InvocationInfo; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Category", delegate () { return (int)CategoryInfo.Category; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Activity", delegate () { return CategoryInfo.Activity; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Reason", delegate () { return CategoryInfo.Reason; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_TargetName", delegate () { return CategoryInfo.TargetName; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_TargetType", delegate () { return CategoryInfo.TargetType; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Message", delegate () { return CategoryInfo.GetMessage(CultureInfo.CurrentCulture); }); + RemotingEncoder.AddNoteProperty(dest, "Exception", () => Exception); + RemotingEncoder.AddNoteProperty(dest, "TargetObject", () => TargetObject); + RemotingEncoder.AddNoteProperty(dest, "FullyQualifiedErrorId", () => FullyQualifiedErrorId); + RemotingEncoder.AddNoteProperty(dest, "InvocationInfo", () => InvocationInfo); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Category", () => (int)CategoryInfo.Category); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Activity", () => CategoryInfo.Activity); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Reason", () => CategoryInfo.Reason); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_TargetName", () => CategoryInfo.TargetName); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_TargetType", () => CategoryInfo.TargetType); + RemotingEncoder.AddNoteProperty(dest, "ErrorCategory_Message", () => CategoryInfo.GetMessage(CultureInfo.CurrentCulture)); if (ErrorDetails != null) { - RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_Message", delegate () { return ErrorDetails.Message; }); - RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_RecommendedAction", delegate () { return ErrorDetails.RecommendedAction; }); + RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_Message", () => ErrorDetails.Message); + RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_RecommendedAction", () => ErrorDetails.RecommendedAction); } if (!serializeExtInfo || this.InvocationInfo == null) @@ -1231,12 +1228,12 @@ private void ToPSObjectForRemoting(PSObject dest, bool serializeExtInfo) { RemotingEncoder.AddNoteProperty(dest, "SerializeExtendedInfo", () => true); this.InvocationInfo.ToPSObjectForRemoting(dest); - RemotingEncoder.AddNoteProperty(dest, "PipelineIterationInfo", delegate () { return PipelineIterationInfo; }); + RemotingEncoder.AddNoteProperty(dest, "PipelineIterationInfo", () => PipelineIterationInfo); } if (!string.IsNullOrEmpty(this.ScriptStackTrace)) { - RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_ScriptStackTrace", delegate () { return this.ScriptStackTrace; }); + RemotingEncoder.AddNoteProperty(dest, "ErrorDetails_ScriptStackTrace", () => this.ScriptStackTrace); } } @@ -1338,7 +1335,7 @@ private void ConstructFromPSObjectForRemoting(PSObject serializedErrorRecord) string errorDetails_ScriptStackTrace = GetNoteValue(serializedErrorRecord, "ErrorDetails_ScriptStackTrace") as string; - RemoteException re = new RemoteException((string.IsNullOrWhiteSpace(exceptionMessage) == false) ? exceptionMessage : errorCategory_Message, serializedException, invocationInfo); + RemoteException re = new RemoteException((!string.IsNullOrWhiteSpace(exceptionMessage)) ? exceptionMessage : errorCategory_Message, serializedException, invocationInfo); // Create ErrorRecord PopulateProperties( @@ -1482,7 +1479,7 @@ internal void SetTargetObject(object target) /// for that ErrorCategory. /// /// never null - public ErrorCategoryInfo CategoryInfo { get => _categoryInfo ?? (_categoryInfo = new ErrorCategoryInfo(this)); } + public ErrorCategoryInfo CategoryInfo { get => _categoryInfo ??= new ErrorCategoryInfo(this); } private ErrorCategoryInfo _categoryInfo; @@ -1636,7 +1633,7 @@ internal bool SerializeExtendedInfo #endregion Public Properties #region Private - private string _errorId; + private readonly string _errorId; #region Exposed by ErrorCategoryInfo internal ErrorCategory _category; @@ -1668,8 +1665,7 @@ private string GetInvocationTypeName() return commandInfo.Name; } - CmdletInfo cmdletInfo = commandInfo as CmdletInfo; - if (cmdletInfo == null) + if (commandInfo is not CmdletInfo cmdletInfo) { return string.Empty; } @@ -1681,7 +1677,7 @@ private string GetInvocationTypeName() #region ToString /// - /// As + /// As /// /// Developer-readable identifier. public override string ToString() @@ -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 @@ -1764,6 +1755,7 @@ public ErrorRecord(Exception exception, string errorId, ErrorCategory errorCateg /// /// is no longer available. /// +#nullable enable public interface IContainsErrorRecord { /// @@ -1792,6 +1784,7 @@ public interface IContainsErrorRecord /// ErrorRecord ErrorRecord { get; } } +#nullable restore /// /// Objects implementing this interface can be used by @@ -1813,6 +1806,7 @@ public interface IContainsErrorRecord /// since the improved /// information about the error may help enable future scenarios. /// +#nullable enable public interface IResourceSupplier { /// @@ -1829,7 +1823,7 @@ public interface IResourceSupplier /// if you want more complex behavior. /// /// Insertions will be inserted into the string with - /// + /// /// to generate the final error message in /// . /// diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index a962f745eac..1c5de607321 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); /// @@ -331,12 +331,12 @@ internal PSLocalEventManager(ExecutionContext context) _context = context; } - private Dictionary _eventSubscribers; - private Dictionary> _engineEventSubscribers; - private Queue _actionQueue; - private ExecutionContext _context; + private readonly Dictionary _eventSubscribers; + private readonly Dictionary> _engineEventSubscribers; + private readonly Queue _actionQueue; + private readonly ExecutionContext _context; private int _nextSubscriptionId = 1; - private double _throttleLimit = 1; + private readonly double _throttleLimit = 1; private int _throttleChecks = 0; // The assembly and module to hold our event registrations @@ -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) { @@ -599,7 +599,7 @@ private void OnElapsedEvent(object source) if (_engineEventSubscribers.TryGetValue(PSEngineEvent.OnIdle, out subscribers) && subscribers.Count > 0) { // We send out on-idle event and keep enabling the timer only if there still are subscribers to the on-idle event - GenerateEvent(PSEngineEvent.OnIdle, null, new object[] { }, null, false, false); + GenerateEvent(PSEngineEvent.OnIdle, null, Array.Empty(), null, false, false); EnableTimer(); } else @@ -640,7 +640,7 @@ private void EnableTimer() #endregion OnIdleProcessing - private static Dictionary s_generatedEventHandlers = new Dictionary(); + private static readonly Dictionary s_generatedEventHandlers = new Dictionary(); private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, string eventName, string sourceIdentifier, PSObject data, bool supportEvent, bool forwardEvent) { @@ -680,7 +680,7 @@ private void ProcessNewSubscriber(PSEventSubscriber subscriber, object source, s } // Retrieve the event from the object - BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase; + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase; eventInfo = sourceType.GetEvent(eventName, bindingFlags); // If we can't find the event, throw an exception @@ -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) @@ -844,7 +841,7 @@ private void UnsubscribeEvent(PSEventSubscriber subscriber, bool skipDraining) Type sourceType = subscriber.SourceObject as Type ?? subscriber.SourceObject.GetType(); - BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase; + const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.IgnoreCase; eventInfo = sourceType.GetEvent(subscriber.EventName, bindingFlags); if ((eventInfo != null) && (existingSubscriber != null)) @@ -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); @@ -953,11 +947,7 @@ protected internal override void ProcessNewEvent(PSEventArgs newEvent, } else { - ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object unused) - { - ProcessNewEventImplementation(newEvent, false); - })); + ThreadPool.QueueUserWorkItem(new WaitCallback((_) => ProcessNewEventImplementation(newEvent, false))); } } @@ -1137,7 +1127,7 @@ private void ProcessPendingActionsImpl() // teardown event. That can result in starvation of // foreground threads that also want to use the runspace. ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object unused) + (_) => { System.Threading.Thread.Sleep(100); this.PulseEngine(); @@ -1168,7 +1158,7 @@ private void AutoUnregisterEventIfNecessary(PSEventSubscriber subscriber) } } - private object _actionProcessingLock = new object(); + private readonly object _actionProcessingLock = new object(); private EventAction _processingAction = null; /// @@ -1297,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); @@ -1511,20 +1501,7 @@ private Type GenerateEventHandler(MethodInfo invokeSignature) /// protected virtual void OnForwardEvent(PSEventArgs e) { - EventHandler eh = ForwardEvent; - - if (eh != null) - { - eh(this, e); - } - } - - /// - /// Destructor for the EventManager class. - /// - ~PSLocalEventManager() - { - Dispose(false); + ForwardEvent?.Invoke(this, e); } /// @@ -1539,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()) { @@ -1561,6 +1535,14 @@ public void Dispose(bool disposing) } } } + + /// + /// Finalizes an instance of the class. + /// + ~PSLocalEventManager() + { + Dispose(false); + } } /// @@ -1569,10 +1551,10 @@ public void Dispose(bool disposing) internal class PSRemoteEventManager : PSEventManager { /// Computer on which the event was generated - private string _computerName; + private readonly string _computerName; /// Runspace on which the event was generated - private Guid _runspaceId; + private readonly Guid _runspaceId; /// /// Creates an event manager for the given runspace. @@ -1598,6 +1580,7 @@ public override List Subscribers /// /// Creates a PowerShell event. + /// /// /// An optional identifier that identifies the source event /// @@ -1610,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 @@ -1666,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); @@ -1677,6 +1659,7 @@ public override IEnumerable GetEventSubscribers(string source /// /// Subscribes to an event on an object. + /// /// /// The source object that defines the event /// @@ -1698,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) { @@ -1707,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 /// @@ -1732,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) { @@ -1741,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 /// @@ -1762,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) { @@ -1771,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 /// @@ -1796,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) { @@ -1805,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); @@ -1824,12 +1806,7 @@ public override void UnsubscribeEvent(PSEventSubscriber subscriber) /// protected virtual void OnForwardEvent(PSEventArgs e) { - EventHandler eh = ForwardEvent; - - if (eh != null) - { - eh(this, e); - } + ForwardEvent?.Invoke(this, e); } } @@ -1853,6 +1830,11 @@ private PSEngineEvent() { } /// public const string OnIdle = "PowerShell.OnIdle"; + /// + /// Called when Debug-Runspace has attached a debugger to the current runspace. + /// + public const string OnDebugAttach = "PowerShell.OnDebugAttach"; + /// /// Called during scriptblock invocation. /// @@ -1866,7 +1848,7 @@ private PSEngineEvent() { } /// /// A HashSet that contains all engine event names. /// - internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke }; + internal static readonly HashSet EngineEvents = new HashSet(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnDebugAttach, OnScriptBlockInvoke }; } /// @@ -1909,9 +1891,17 @@ internal PSEventSubscriber(ExecutionContext context, int id, object source, /// Creates an instance of the PSEventSubscriber /// class. Additionally supports an Action scriptblock. /// - internal PSEventSubscriber(ExecutionContext context, int id, object source, - string eventName, string sourceIdentifier, ScriptBlock action, bool supportEvent, bool forwardEvent, int maxTriggerCount) : - this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount) + internal PSEventSubscriber( + ExecutionContext context, + int id, + object source, + string eventName, + string sourceIdentifier, + ScriptBlock action, + bool supportEvent, + bool forwardEvent, + int maxTriggerCount) + : this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount) { // Create the bound scriptblock, and job. if (action != null) @@ -1938,14 +1928,22 @@ internal void RegisterJob() /// Creates an instance of the PSEventSubscriber /// class. Additionally supports an Action scriptblock. /// - internal PSEventSubscriber(ExecutionContext context, int id, object source, - string eventName, string sourceIdentifier, PSEventReceivedEventHandler handlerDelegate, bool supportEvent, bool forwardEvent, int maxTriggerCount) : - this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount) + internal PSEventSubscriber( + ExecutionContext context, + int id, + object source, + string eventName, + string sourceIdentifier, + PSEventReceivedEventHandler handlerDelegate, + bool supportEvent, + bool forwardEvent, + int maxTriggerCount) + : this(context, id, source, eventName, sourceIdentifier, supportEvent, forwardEvent, maxTriggerCount) { HandlerDelegate = handlerDelegate; } - private ExecutionContext _context; + private readonly ExecutionContext _context; /// /// Create a bound script block. @@ -2011,7 +2009,7 @@ private ScriptBlock CreateBoundScriptBlock(ScriptBlock scriptAction) /// /// Gets whether the event should be unregistered. /// - internal bool AutoUnregister { get; private set; } + internal bool AutoUnregister { get; } /// /// Indicate how many new should be added to the action queue. @@ -2040,12 +2038,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) @@ -2067,10 +2078,7 @@ public override int GetHashCode() internal void OnPSEventUnsubscribed(object sender, PSEventUnsubscribedEventArgs e) { - if (Unsubscribed != null) - { - Unsubscribed(sender, e); - } + Unsubscribed?.Invoke(sender, e); } } @@ -2092,6 +2100,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. /// @@ -2105,7 +2114,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; @@ -2351,7 +2359,7 @@ public class PSEventArgsCollection : IEnumerable /// public event PSEventReceivedEventHandler PSEventReceived; - private List _eventCollection = new List(); + private readonly List _eventCollection = new List(); /// /// Add add an event to the collection. @@ -2362,10 +2370,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); @@ -2404,11 +2409,7 @@ public PSEventArgs this[int index] private void OnPSEventReceived(object sender, PSEventArgs e) { - PSEventReceivedEventHandler eventHandler = PSEventReceived; - if (eventHandler != null) - { - eventHandler(sender, e); - } + PSEventReceived?.Invoke(sender, e); } /// @@ -2464,6 +2465,7 @@ public class PSEventJob : Job { /// /// Creates a new instance of the PSEventJob class. + /// /// /// The event manager that controls the event subscriptions /// @@ -2476,14 +2478,16 @@ public class PSEventJob : Job /// /// The name of the job /// - /// - public PSEventJob(PSEventManager eventManager, PSEventSubscriber subscriber, ScriptBlock action, string name) : - base(action == null ? null : action.ToString(), name) + public PSEventJob( + PSEventManager eventManager, + PSEventSubscriber subscriber, + ScriptBlock action, + 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; @@ -2491,8 +2495,8 @@ public PSEventJob(PSEventManager eventManager, PSEventSubscriber subscriber, Scr _subscriber = subscriber; } - private PSEventManager _eventManager = null; - private PSEventSubscriber _subscriber = null; + private readonly PSEventManager _eventManager = null; + private readonly PSEventSubscriber _subscriber = null; private int _highestErrorIndex = 0; /// @@ -2550,13 +2554,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)) @@ -2597,7 +2601,7 @@ internal void Invoke(PSEventSubscriber eventSubscriber, PSEventArgs eventArgs) catch (Exception e) { // Catch-all OK. This is a third-party call-out. - if (!(e is PipelineStoppedException)) + if (e is not PipelineStoppedException) { LogErrorsAndOutput(results, actionState); SetJobState(JobState.Failed); diff --git a/src/System.Management.Automation/engine/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index 98f0b3deb5a..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(); } /// @@ -83,7 +73,10 @@ internal int PSDebugTraceLevel return IgnoreScriptDebug ? 0 : _debugTraceLevel; } - set { _debugTraceLevel = value; } + set + { + _debugTraceLevel = value; + } } private int _debugTraceLevel; @@ -100,7 +93,10 @@ internal bool PSDebugTraceStep return !IgnoreScriptDebug && _debugTraceStep; } - set { _debugTraceStep = value; } + set + { + _debugTraceStep = value; + } } private bool _debugTraceStep; @@ -108,14 +104,9 @@ 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) - : false; + return (context != null) && context.IsStrictVersion(majorVersion); } /// /// Check to see a specific version of strict mode is enabled. The check is always scoped, @@ -172,7 +163,7 @@ internal bool ShouldTraceStatement /// trace flag. /// /// The current state of the IgnoreScriptDebug flag. - internal bool IgnoreScriptDebug { set; get; } = true; + internal bool IgnoreScriptDebug { get; set; } = true; /// /// Gets the automation engine instance. @@ -201,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. /// @@ -249,10 +206,7 @@ internal ProviderNames ProviderNames { get { - if (_providerNames == null) - { - _providerNames = new SingleShellProviderNames(); - } + _providerNames ??= new SingleShellProviderNames(); return _providerNames; } @@ -409,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; @@ -426,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 { @@ -488,7 +441,7 @@ internal bool UseFullLanguageModeInDebugger { get { - return InitialSessionState != null ? InitialSessionState.UseFullLanguageModeInDebugger : false; + return InitialSessionState != null && InitialSessionState.UseFullLanguageModeInDebugger; } } @@ -524,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 @@ -555,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; } /// @@ -642,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 @@ -666,7 +612,7 @@ internal bool GetBooleanPreference(VariablePath preferenceVariablePath, bool def /// internal HelpSystem HelpSystem { - get { return _helpSystem ?? (_helpSystem = new HelpSystem(this)); } + get { return _helpSystem ??= new HelpSystem(this); } } private HelpSystem _helpSystem; @@ -679,6 +625,7 @@ internal HelpSystem HelpSystem #endregion internal Dictionary CustomArgumentCompleters { get; set; } + internal Dictionary NativeArgumentCompleters { get; set; } /// @@ -686,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) { @@ -744,7 +692,7 @@ internal InternalHost InternalHost /// internal EngineIntrinsics EngineIntrinsics { - get { return _engineIntrinsics ?? (_engineIntrinsics = new EngineIntrinsics(this)); } + get { return _engineIntrinsics ??= new EngineIntrinsics(this); } } private EngineIntrinsics _engineIntrinsics; @@ -772,11 +720,11 @@ internal EngineIntrinsics EngineIntrinsics internal class SavedContextData { - private bool _stepScript; - private bool _ignoreScriptDebug; - private int _PSDebug; + private readonly bool _stepScript; + private readonly bool _ignoreScriptDebug; + private readonly int _PSDebug; - private Pipe _shellFunctionErrorOutputPipe; + private readonly Pipe _shellFunctionErrorOutputPipe; public SavedContextData(ExecutionContext context) { @@ -818,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. @@ -875,15 +818,13 @@ internal void ResetRedirection() internal void AppendDollarError(object obj) { ErrorRecord objAsErrorRecord = obj as ErrorRecord; - if (objAsErrorRecord == null && !(obj is 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; @@ -906,7 +847,7 @@ internal void AppendDollarError(object obj) const int maxErrorCount = 256; int numToErase = arraylist.Count - (maxErrorCount - 1); - if (0 < numToErase) + if (numToErase > 0) { arraylist.RemoveRange( maxErrorCount - 1, @@ -1204,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; } @@ -1311,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.TryAdd(key, assembly); + } - AssemblyCache.Remove(loadedAssembly.GetName().Name); + /// + /// 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) @@ -1446,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)); } @@ -1476,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); } } @@ -1511,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); } } @@ -1531,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()); } } @@ -1597,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); @@ -1621,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 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 + private static readonly object lockObject = new object(); } /// @@ -1698,5 +1707,5 @@ internal enum EngineState /// Engine is stopped. /// Stopped = 4 - }; + } } diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs index c15d8628cb9..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; @@ -70,7 +71,7 @@ protected override void ProcessRecord() } } - internal class ExperimentalFeatureConfigHelper + internal static class ExperimentalFeatureConfigHelper { internal static void UpdateConfig(PSCmdlet cmdlet, string[] name, ConfigScope scope, bool enable) { @@ -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 f6993a03a0a..7e17ec43137 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -21,6 +20,8 @@ public class ExperimentalFeature #region Const Members internal const string EngineSource = "PSEngine"; + internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); + internal const string PSProfileDSCResource = "PSProfileDSCResource"; #endregion @@ -104,30 +105,22 @@ 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"), + name: "PSLoadAssemblyFromNativeCode", + description: "Expose an API to allow assembly loading from native code"), new ExperimentalFeature( - name: "PSCommandNotFoundSuggestion", - description: "Recommend potential commands based on fuzzy search on a CommandNotFoundException"), -#if UNIX + name: PSSerializeJSONLongEnumAsNumber, + description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ), new ExperimentalFeature( - name: "PSUnixFileStat", - description: "Provide unix permission information for files and directories"), -#endif - new ExperimentalFeature( - name: "PSNullConditionalOperators", - description: "Support the null conditional member access operators in PowerShell language"), - 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: PSProfileDSCResource, + description: "DSC v3 resources for managing PowerShell profile." + ) }; + 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'. @@ -147,6 +140,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 @@ -154,7 +161,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) @@ -185,7 +195,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; } /// @@ -342,20 +354,21 @@ internal static void ValidateArguments(string experimentName, ExperimentAction e { if (string.IsNullOrEmpty(experimentName)) { - string paramName = nameof(experimentName); + const string paramName = nameof(experimentName); throw PSTraceSource.NewArgumentNullException(paramName, Metadata.ArgumentNullOrEmpty, paramName); } if (experimentAction == ExperimentAction.None) { - string paramName = nameof(experimentAction); - string invalidMember = nameof(ExperimentAction.None); + const string paramName = nameof(experimentAction); + const string invalidMember = nameof(ExperimentAction.None); string validMembers = StringUtil.Format("{0}, {1}", ExperimentAction.Hide, ExperimentAction.Show); throw PSTraceSource.NewArgumentException(paramName, Metadata.InvalidEnumArgument, invalidMember, paramName, validMembers); } } internal bool ToHide => EffectiveAction == ExperimentAction.Hide; + internal bool ToShow => EffectiveAction == ExperimentAction.Show; /// diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs index ff1c964fc4c..38525cb54ef 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/GetExperimentalFeatureCommand.cs @@ -14,6 +14,7 @@ namespace Microsoft.PowerShell.Commands /// Implements Get-ExperimentalFeature cmdlet. /// [Cmdlet(VerbsCommon.Get, "ExperimentalFeature", HelpUri = "https://go.microsoft.com/fwlink/?linkid=2096786")] + [OutputType(typeof(ExperimentalFeature))] public class GetExperimentalFeatureCommand : PSCmdlet { /// @@ -87,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 542ad8bbf7e..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 @@ -18,7 +17,8 @@ public class ExtendedTypeSystemException : RuntimeException /// Initializes a new instance of ExtendedTypeSystemException with the message set /// to typeof(ExtendedTypeSystemException).FullName. /// - public ExtendedTypeSystemException() : base(typeof(ExtendedTypeSystemException).FullName) + public ExtendedTypeSystemException() + : base(typeof(ExtendedTypeSystemException).FullName) { } @@ -26,7 +26,8 @@ public ExtendedTypeSystemException() : base(typeof(ExtendedTypeSystemException). /// Initializes a new instance of ExtendedTypeSystemException setting the message. /// /// The exception's message. - public ExtendedTypeSystemException(string message) : base(message) + public ExtendedTypeSystemException(string message) + : base(message) { } @@ -34,8 +35,9 @@ public ExtendedTypeSystemException(string message) : base(message) /// Initializes a new instance of ExtendedTypeSystemException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public ExtendedTypeSystemException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public ExtendedTypeSystemException(string message, Exception innerException) + : base(message, innerException) { } @@ -46,9 +48,14 @@ public ExtendedTypeSystemException(string message, Exception innerException) : b /// The inner exception, null for none. /// Resource string. /// Arguments to the resource string. - internal ExtendedTypeSystemException(string errorId, Exception innerException, string resourceString, - params object[] arguments) : - base(StringUtil.Format(resourceString, arguments), innerException) + internal ExtendedTypeSystemException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base( + StringUtil.Format(resourceString, arguments), + innerException) { SetErrorId(errorId); } @@ -59,8 +66,9 @@ internal ExtendedTypeSystemException(string errorId, Exception innerException, s /// /// 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 @@ -72,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"; @@ -86,7 +93,8 @@ public class MethodException : ExtendedTypeSystemException /// Initializes a new instance of MethodException with the message set /// to typeof(MethodException).FullName. /// - public MethodException() : base(typeof(MethodException).FullName) + public MethodException() + : base(typeof(MethodException).FullName) { } @@ -94,7 +102,8 @@ public MethodException() : base(typeof(MethodException).FullName) /// Initializes a new instance of MethodException setting the message. /// /// The exception's message. - public MethodException(string message) : base(message) + public MethodException(string message) + : base(message) { } @@ -102,8 +111,9 @@ public MethodException(string message) : base(message) /// Initializes a new instance of MethodException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public MethodException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public MethodException(string message, Exception innerException) + : base(message, innerException) { } @@ -114,9 +124,12 @@ public MethodException(string message, Exception innerException) : base(message, /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal MethodException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal MethodException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -126,9 +139,10 @@ internal MethodException(string errorId, Exception innerException, /// /// 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 @@ -139,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"; @@ -151,7 +164,8 @@ public class MethodInvocationException : MethodException /// Initializes a new instance of MethodInvocationException with the message set /// to typeof(MethodInvocationException).FullName. /// - public MethodInvocationException() : base(typeof(MethodInvocationException).FullName) + public MethodInvocationException() + : base(typeof(MethodInvocationException).FullName) { } @@ -159,7 +173,8 @@ public MethodInvocationException() : base(typeof(MethodInvocationException).Full /// Initializes a new instance of MethodInvocationException setting the message. /// /// The exception's message. - public MethodInvocationException(string message) : base(message) + public MethodInvocationException(string message) + : base(message) { } @@ -167,8 +182,9 @@ public MethodInvocationException(string message) : base(message) /// Initializes a new instance of MethodInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public MethodInvocationException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public MethodInvocationException(string message, Exception innerException) + : base(message, innerException) { } @@ -179,9 +195,12 @@ public MethodInvocationException(string message, Exception innerException) : bas /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal MethodInvocationException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal MethodInvocationException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -191,9 +210,10 @@ internal MethodInvocationException(string errorId, Exception innerException, /// /// 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 @@ -204,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"; @@ -214,7 +233,8 @@ public class GetValueException : ExtendedTypeSystemException /// Initializes a new instance of GetValueException with the message set /// to typeof(GetValueException).FullName. /// - public GetValueException() : base(typeof(GetValueException).FullName) + public GetValueException() + : base(typeof(GetValueException).FullName) { } @@ -222,7 +242,8 @@ public GetValueException() : base(typeof(GetValueException).FullName) /// Initializes a new instance of GetValueException setting the message. /// /// The exception's message. - public GetValueException(string message) : base(message) + public GetValueException(string message) + : base(message) { } @@ -230,8 +251,9 @@ public GetValueException(string message) : base(message) /// Initializes a new instance of GetValueException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public GetValueException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public GetValueException(string message, Exception innerException) + : base(message, innerException) { } @@ -242,9 +264,12 @@ public GetValueException(string message, Exception innerException) : base(messag /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal GetValueException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal GetValueException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -254,9 +279,10 @@ internal GetValueException(string errorId, Exception innerException, /// /// 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 @@ -267,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 @@ -293,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) { @@ -306,9 +331,12 @@ public PropertyNotFoundException(string message, Exception innerException) /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal PropertyNotFoundException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal PropertyNotFoundException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -318,12 +346,12 @@ internal PropertyNotFoundException(string errorId, Exception innerException, /// /// 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 } @@ -331,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"; @@ -341,7 +368,8 @@ public class GetValueInvocationException : GetValueException /// Initializes a new instance of GetValueInvocationException with the message set /// to typeof(GetValueInvocationException).FullName. /// - public GetValueInvocationException() : base(typeof(GetValueInvocationException).FullName) + public GetValueInvocationException() + : base(typeof(GetValueInvocationException).FullName) { } @@ -349,7 +377,8 @@ public GetValueInvocationException() : base(typeof(GetValueInvocationException). /// Initializes a new instance of GetValueInvocationException setting the message. /// /// The exception's message. - public GetValueInvocationException(string message) : base(message) + public GetValueInvocationException(string message) + : base(message) { } @@ -357,8 +386,9 @@ public GetValueInvocationException(string message) : base(message) /// Initializes a new instance of GetValueInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public GetValueInvocationException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public GetValueInvocationException(string message, Exception innerException) + : base(message, innerException) { } @@ -369,9 +399,12 @@ public GetValueInvocationException(string message, Exception innerException) : b /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal GetValueInvocationException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal GetValueInvocationException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -381,9 +414,10 @@ internal GetValueInvocationException(string errorId, Exception innerException, /// /// 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 @@ -394,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 @@ -402,7 +435,8 @@ public class SetValueException : ExtendedTypeSystemException /// Initializes a new instance of SetValueException with the message set /// to typeof(SetValueException).FullName. /// - public SetValueException() : base(typeof(SetValueException).FullName) + public SetValueException() + : base(typeof(SetValueException).FullName) { } @@ -410,7 +444,8 @@ public SetValueException() : base(typeof(SetValueException).FullName) /// Initializes a new instance of SetValueException setting the message. /// /// The exception's message. - public SetValueException(string message) : base(message) + public SetValueException(string message) + : base(message) { } @@ -418,8 +453,9 @@ public SetValueException(string message) : base(message) /// Initializes a new instance of SetValueException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public SetValueException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public SetValueException(string message, Exception innerException) + : base(message, innerException) { } @@ -430,9 +466,12 @@ public SetValueException(string message, Exception innerException) : base(messag /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal SetValueException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal SetValueException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -442,9 +481,10 @@ internal SetValueException(string errorId, Exception innerException, /// /// 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 @@ -455,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 @@ -463,7 +502,8 @@ public class SetValueInvocationException : SetValueException /// Initializes a new instance of SetValueInvocationException with the message set /// to typeof(SetValueInvocationException).FullName. /// - public SetValueInvocationException() : base(typeof(SetValueInvocationException).FullName) + public SetValueInvocationException() + : base(typeof(SetValueInvocationException).FullName) { } @@ -471,7 +511,8 @@ public SetValueInvocationException() : base(typeof(SetValueInvocationException). /// Initializes a new instance of SetValueInvocationException setting the message. /// /// The exception's message. - public SetValueInvocationException(string message) : base(message) + public SetValueInvocationException(string message) + : base(message) { } @@ -479,8 +520,9 @@ public SetValueInvocationException(string message) : base(message) /// Initializes a new instance of SetValueInvocationException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public SetValueInvocationException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public SetValueInvocationException(string message, Exception innerException) + : base(message, innerException) { } @@ -491,9 +533,12 @@ public SetValueInvocationException(string message, Exception innerException) : b /// The inner exception. /// Resource string. /// Arguments to the resource string. - internal SetValueInvocationException(string errorId, Exception innerException, - string resourceString, params object[] arguments) : - base(errorId, innerException, resourceString, arguments) + internal SetValueInvocationException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : base(errorId, innerException, resourceString, arguments) { } @@ -503,9 +548,10 @@ internal SetValueInvocationException(string errorId, Exception innerException, /// /// 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 @@ -516,60 +562,42 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. - protected PSInvalidCastException(SerializationInfo info, StreamingContext context) : base(info, context) + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected PSInvalidCastException(SerializationInfo info, StreamingContext context) { - _errorId = info.GetString("ErrorId"); + throw new NotSupportedException(); } - #endregion Serialization - /// /// Initializes a new instance of PSInvalidCastException with the message set /// to typeof(PSInvalidCastException).FullName. /// - public PSInvalidCastException() : base(typeof(PSInvalidCastException).FullName) + public PSInvalidCastException() + : base(typeof(PSInvalidCastException).FullName) { } /// /// Initializes a new instance of PSInvalidCastException setting the message. /// /// The exception's message. - public PSInvalidCastException(string message) : base(message) + public PSInvalidCastException(string message) + : base(message) { } /// /// Initializes a new instance of PSInvalidCastException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public PSInvalidCastException(string message, Exception innerException) : base(message, innerException) + /// The exception's inner exception. + public PSInvalidCastException(string message, Exception innerException) + : base(message, innerException) { } @@ -579,8 +607,14 @@ internal PSInvalidCastException(string errorId, string message, Exception innerE _errorId = errorId; } - internal PSInvalidCastException(string errorId, Exception innerException, string resourceString, params object[] arguments) - : this(errorId, StringUtil.Format(resourceString, arguments), innerException) + internal PSInvalidCastException( + string errorId, + Exception innerException, + string resourceString, + params object[] arguments) + : this( + errorId, StringUtil.Format(resourceString, arguments), + innerException) { } @@ -591,21 +625,17 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "PSInvalidCastException"; + private readonly string _errorId = "PSInvalidCastException"; } } - diff --git a/src/System.Management.Automation/engine/ExternalScriptInfo.cs b/src/System.Management.Automation/engine/ExternalScriptInfo.cs index d87e3027f2e..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,12 +195,18 @@ public override SessionStateEntryVisibility Visibility { get { - if (Context == null) return SessionStateEntryVisibility.Public; + if (Context == null) + { + return SessionStateEntryVisibility.Public; + } return Context.EngineSessionState.CheckScriptVisibility(_path); } - set { throw PSTraceSource.NewNotImplementedException(); } + set + { + throw PSTraceSource.NewNotImplementedException(); + } } /// @@ -358,9 +371,8 @@ internal override CommandMetadata CommandMetadata { get { - return _commandMetadata ?? - (_commandMetadata = - new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS())); + return _commandMetadata ??= + new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS()); } } @@ -382,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; @@ -403,7 +415,7 @@ internal string RequiresApplicationID get { var data = GetRequiresData(); - return data == null ? null : data.RequiredApplicationId; + return data?.RequiredApplicationId; } } @@ -417,7 +429,7 @@ internal Version RequiresPSVersion get { var data = GetRequiresData(); - return data == null ? null : data.RequiredPSVersion; + return data?.RequiredPSVersion; } } @@ -426,7 +438,7 @@ internal IEnumerable RequiresPSEditions get { var data = GetRequiresData(); - return data == null ? null : data.RequiredPSEditions; + return data?.RequiredPSEditions; } } @@ -435,7 +447,7 @@ internal IEnumerable RequiresModules get { var data = GetRequiresData(); - return data == null ? null : data.RequiredModules; + return data?.RequiredModules; } } @@ -444,7 +456,7 @@ internal bool RequiresElevation get { var data = GetRequiresData(); - return data == null ? false : data.IsElevationRequired; + return data != null && data.IsElevationRequired; } } @@ -453,15 +465,6 @@ internal uint PSVersionLineNumber get { return 0; } } - internal IEnumerable RequiresPSSnapIns - { - get - { - var data = GetRequiresData(); - return data == null ? null : data.RequiresPSSnapIns; - } - } - /// /// Gets the original contents of the script. /// @@ -513,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)); } } } @@ -587,7 +611,6 @@ internal ScriptRequiresSyntaxException(string message) /// /// Defines the name and version tuple of a PSSnapin. /// - [Serializable] public class PSSnapInSpecification { internal PSSnapInSpecification(string psSnapinName) @@ -608,4 +631,3 @@ internal PSSnapInSpecification(string psSnapinName) public Version Version { get; internal set; } } } - diff --git a/src/System.Management.Automation/engine/FunctionInfo.cs b/src/System.Management.Automation/engine/FunctionInfo.cs index 8afe41ece15..70ab80e8ca4 100644 --- a/src/System.Management.Automation/engine/FunctionInfo.cs +++ b/src/System.Management.Automation/engine/FunctionInfo.cs @@ -265,7 +265,7 @@ internal void Update(ScriptBlock newFunction, bool force, ScopedItemOptions opti } /// - /// Returns true if this function uses cmdlet binding mode for its parameters; otherwise returns false. + /// Returns if this function uses cmdlet binding mode for its parameters; otherwise returns . /// public bool CmdletBinding { @@ -277,7 +277,7 @@ public bool CmdletBinding /// /// Gets the name of the default parameter set. - /// Returns null if this function doesn't use cmdlet parameter binding or if the default parameter set wasn't specified. + /// Returns if this function doesn't use cmdlet parameter binding or if the default parameter set wasn't specified. /// public string DefaultParameterSet { @@ -479,9 +479,8 @@ internal override CommandMetadata CommandMetadata { get { - return _commandMetadata ?? - (_commandMetadata = - new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS())); + return _commandMetadata ??= + new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS()); } } diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index afc2b12393b..10e8334021b 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. /// @@ -280,14 +295,14 @@ public SwitchParameter ListImported [ValidateNotNullOrEmpty] public string[] ParameterName { - get { return _parameterNames; } + get + { + return _parameterNames; + } set { - if (value == null) - { - throw new ArgumentNullException("value"); - } + ArgumentNullException.ThrowIfNull(value); _parameterNames = value; _parameterNameWildcards = SessionStateUtilities.CreateWildcardsFromStrings( @@ -314,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); @@ -350,7 +362,14 @@ public PSTypeName[] ParameterType [Parameter(ParameterSetName = "AllCommandSet")] public SwitchParameter UseFuzzyMatching { get; set; } - private 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. @@ -372,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) { @@ -402,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) { @@ -484,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); @@ -511,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)) { @@ -546,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 @@ -558,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. @@ -647,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 @@ -688,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( @@ -785,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; @@ -862,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); @@ -940,17 +968,17 @@ 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; - do + while (true) { try { @@ -1033,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); @@ -1057,7 +1087,7 @@ private bool FindCommandForName(SearchResolutionOptions options, string commandN break; } } - } while (true); + } if (All) { @@ -1156,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 @@ -1274,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; @@ -1320,7 +1354,9 @@ private bool IsCommandMatch(ref CommandInfo current, out bool isDuplicate) } } - if (ArgumentList != null && !(current is CmdletInfo || current is IScriptCommandInfo)) + if (ArgumentList != null + && current is not CmdletInfo + && current is not IScriptCommandInfo) { // If current is not a cmdlet or script, we need to throw a terminating error. ThrowTerminatingError( @@ -1421,7 +1457,7 @@ private IEnumerable GetMatchingCommandsFromModules(string commandNa { PSModuleInfo module = null; - if (Context.EngineSessionState.ModuleTable.TryGetValue(Context.EngineSessionState.ModuleTableKeys[i], out module) == false) + if (!Context.EngineSessionState.ModuleTable.TryGetValue(Context.EngineSessionState.ModuleTableKeys[i], out module)) { Dbg.Assert(false, "ModuleTableKeys should be in sync with ModuleTable"); } @@ -1487,22 +1523,31 @@ private IEnumerable GetMatchingCommandsFromModules(string commandNa private bool IsCommandInResult(CommandInfo command) { bool isPresent = false; - bool commandHasModule = command.Module != null; - foreach (CommandInfo commandInfo in _accumulatedResults) - { - if ((command.CommandType == commandInfo.CommandType && - (string.Equals(command.Name, commandInfo.Name, StringComparison.OrdinalIgnoreCase) || - // If the command has been imported with a prefix, then just checking the names for duplication will not be enough. - // Hence, an additional check is done with the prefix information - string.Equals(ModuleCmdletBase.RemovePrefixFromCommandName(commandInfo.Name, commandInfo.Prefix), command.Name, StringComparison.OrdinalIgnoreCase)) - ) && commandInfo.Module != null && commandHasModule && - ( // We do reference equal comparison if both command are imported. If either one is not imported, we compare the module path - (commandInfo.IsImported && command.IsImported && commandInfo.Module.Equals(command.Module)) || - ((!commandInfo.IsImported || !command.IsImported) && commandInfo.Module.Path.Equals(command.Module.Path, StringComparison.OrdinalIgnoreCase)) - )) + + if (command.Module is not null) + { + foreach (CommandInfo commandInfo in _accumulatedResults) { - isPresent = true; - break; + if (commandInfo.Module is null || commandInfo.CommandType != command.CommandType) + { + continue; + } + + // We do reference equal comparison if both command are imported. If either one is not imported, we compare the module path + if ((!commandInfo.IsImported || !command.IsImported || !commandInfo.Module.Equals(command.Module)) + && ((commandInfo.IsImported && command.IsImported) || !commandInfo.Module.Path.Equals(command.Module.Path, StringComparison.OrdinalIgnoreCase))) + { + continue; + } + + // If the command has been imported with a prefix, then just checking the names for duplication will not be enough. + // Hence, an additional check is done with the prefix information + if (commandInfo.Name.Equals(command.Name, StringComparison.OrdinalIgnoreCase) + || ModuleCmdletBase.RemovePrefixFromCommandName(commandInfo.Name, commandInfo.Prefix).Equals(command.Name, StringComparison.OrdinalIgnoreCase)) + { + isPresent = true; + break; + } } } @@ -1513,7 +1558,7 @@ private bool IsCommandInResult(CommandInfo command) #region Members - private Dictionary _commandsWritten = + private readonly Dictionary _commandsWritten = new Dictionary(StringComparer.OrdinalIgnoreCase); private List _accumulatedResults = new List(); @@ -1522,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(); @@ -1630,8 +1676,7 @@ private static PSObject GetParameterType(Type parameterType) new ArrayList(Enum.GetValues(parameterType)) : new ArrayList(); returnParameterType.Properties.Add(new PSNoteProperty("EnumValues", enumValues)); - bool hasFlagAttribute = (isArray) ? - ((parameterType.GetCustomAttributes(typeof(FlagsAttribute), true)).Length > 0) : false; + bool hasFlagAttribute = (isArray) && ((parameterType.GetCustomAttributes(typeof(FlagsAttribute), true)).Length > 0); returnParameterType.Properties.Add(new PSNoteProperty("HasFlagAttribute", hasFlagAttribute)); // Recurse into array elements. @@ -1649,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 cf5de2b20ab..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 @@ -589,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. /// /// @@ -614,6 +617,6 @@ public interface ICommandRuntime2 : ICommandRuntime /// performed, and the Cmdlet should move on to the next target resource. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1045:DoNotPassTypesByReference")] - bool ShouldContinue(string query, string caption, bool hasSecurityImpact, ref bool yesToAll, ref bool noToAll); + bool ShouldContinue(string? query, string? caption, bool hasSecurityImpact, ref bool yesToAll, ref bool noToAll); } } diff --git a/src/System.Management.Automation/engine/InformationRecord.cs b/src/System.Management.Automation/engine/InformationRecord.cs index 81a9789bb81..87a3d9cda9d 100644 --- a/src/System.Management.Automation/engine/InformationRecord.cs +++ b/src/System.Management.Automation/engine/InformationRecord.cs @@ -14,8 +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 { /// @@ -30,7 +29,7 @@ public InformationRecord(object messageData, string source) this.TimeGenerated = DateTime.Now; this.NativeThreadId = PsUtils.GetNativeThreadId(); - this.ManagedThreadId = (uint)System.Threading.Thread.CurrentThread.ManagedThreadId; + this.ManagedThreadId = (uint)Environment.CurrentManagedThreadId; } private InformationRecord() { } @@ -82,7 +81,7 @@ internal InformationRecord(InformationRecord baseRecord) [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] public List Tags { - get { return _tags ?? (_tags = new List()); } + get { return _tags ??= new List(); } internal set { _tags = value; } } @@ -97,20 +96,21 @@ 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; } - set { _user = value; } + set + { + _user = value; + } } private string _user; @@ -121,7 +121,7 @@ public string User [DataMember] public string Computer { - get { return this._computerName ?? (this._computerName = PsUtils.GetHostName()); } + get { return this._computerName ??= PsUtils.GetHostName(); } set { this._computerName = value; } } @@ -138,13 +138,16 @@ public uint ProcessId { if (!this._processId.HasValue) { - this._processId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; + this._processId = (uint)Environment.ProcessId; } return this._processId.Value; } - set { _processId = value; } + set + { + _processId = value; + } } private uint? _processId; diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 0d2e1274aa6..a6d50f47443 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -26,7 +26,7 @@ namespace System.Management.Automation.Runspaces { - internal class EarlyStartup + internal static class EarlyStartup { internal static void Init() { @@ -44,25 +44,28 @@ internal static void Init() // We shouldn't create too many tasks. #if !UNIX - // Amsi initialize can be a little slow - Task.Run(() => - { - AmsiUtils.WinScanContent(content: string.Empty, sourceMetadata: string.Empty, warmUp: true); - }); + // 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; }); } } @@ -413,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; @@ -649,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; } @@ -806,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; @@ -952,7 +955,7 @@ public override InitialSessionStateEntry Clone() /// public Collection Attributes { - get { return _attributes ?? (_attributes = new Collection()); } + get { return _attributes ??= new Collection(); } } private Collection _attributes; @@ -977,10 +980,7 @@ public InitialSessionStateEntryCollection() /// public InitialSessionStateEntryCollection(IEnumerable items) { - if (items == null) - { - throw new ArgumentNullException(nameof(items)); - } + ArgumentNullException.ThrowIfNull(items); _internalCollection = new Collection(); @@ -1151,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) { @@ -1189,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) { @@ -1206,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) { @@ -1250,10 +1241,10 @@ IEnumerator System.Collections.Generic.IEnumerable.GetEnumerator() return _internalCollection.GetEnumerator(); } - private Collection _internalCollection; + private readonly Collection _internalCollection; // object to use for locking - private object _syncObject = new object(); + private readonly object _syncObject = new object(); } /// @@ -1315,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); @@ -1330,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) { - Remoting.DISCPowerShellConfiguration discConfiguration = new Remoting.DISCPowerShellConfiguration(path, 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) + { + 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); } @@ -1349,7 +1378,7 @@ public static InitialSessionState CreateFromSessionConfigurationFile(string path public static InitialSessionState CreateRestricted(SessionCapabilities sessionCapabilities) { // only remote server has been requested - if (SessionCapabilities.RemoteServer == sessionCapabilities) + if (sessionCapabilities == SessionCapabilities.RemoteServer) { return CreateRestrictedForRemoteServer(); } @@ -1437,9 +1466,6 @@ private static InitialSessionState CreateRestrictedForRemoteServer() return iss; } - // Porting note: moved to Platform so we have one list to maintain - private static string[] s_PSCoreFormatFileNames = Platform.FormatFileNames.ToArray(); - private static void IncludePowerShellCoreFormats(InitialSessionState iss) { string psHome = Utils.DefaultPowerShellAppBase; @@ -1449,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))); } @@ -1480,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; @@ -1543,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); - } } } @@ -1669,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); @@ -1837,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) { @@ -1865,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) { @@ -1896,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) { @@ -2116,9 +2124,9 @@ public virtual HashSet StartupScripts } } - private HashSet _startupScripts = new HashSet(); + private HashSet _startupScripts; - private object _syncObject = new object(); + private readonly object _syncObject = new object(); internal void Bind(ExecutionContext context, bool updateOnly, PSModuleInfo module, bool noClobber, bool local, bool setLocation) { @@ -2416,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) { @@ -2587,7 +2600,7 @@ HashSet GetPublicCommands() return null; } - private string[] GetModulesForUnResolvedCommands(IEnumerable unresolvedCommands, ExecutionContext context) + private static string[] GetModulesForUnResolvedCommands(IEnumerable unresolvedCommands, ExecutionContext context) { Collection modulesToImport = new Collection(); HashSet commandsToResolve = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -2743,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; @@ -2847,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); } @@ -2904,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; @@ -2949,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 { @@ -2966,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 { @@ -2974,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 { @@ -3023,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) { @@ -3074,11 +3110,13 @@ private RunspaceOpenModuleLoadException ProcessModulesToImport( /// /// /// + /// /// - private IEnumerable LookupCommands( + private static IEnumerable LookupCommands( string commandPattern, string moduleName, - ExecutionContext context) + ExecutionContext context, + List processedModules) { bool isWildCardPattern = WildcardPattern.ContainsWildcardCharacters(commandPattern); var searchOptions = isWildCardPattern ? @@ -3092,7 +3130,11 @@ private 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)) @@ -3122,13 +3164,43 @@ private 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()) { @@ -3165,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 @@ -3173,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 { @@ -3368,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); } } @@ -3386,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 @@ -3603,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) @@ -3630,7 +3609,7 @@ internal void UpdateTypes(ExecutionContext context, bool updateOnly) } } - if (errors.Count > 0) + if (!errors.IsEmpty) { var allErrors = new StringBuilder(); allErrors.Append('\n'); @@ -3739,7 +3718,7 @@ internal void UpdateFormats(ExecutionContext context, bool update) // if this is the case... foreach (PSSnapInTypeAndFormatErrors entry in entries) { - if (entry.Errors != null && entry.Errors.Count > 0) + if (entry.Errors != null && !entry.Errors.IsEmpty) { foreach (string error in entry.Errors) { @@ -3795,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); @@ -3808,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; @@ -3868,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; @@ -3930,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) { @@ -3992,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 (defaultSnapin.Name.Equals(psSnapinName, StringComparison.OrdinalIgnoreCase)) - { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(defaultSnapin); - } - } - - PSSnapInInfo importedSnapin = null; - if (ImportedSnapins.TryGetValue(psSnapinName, out importedSnapin)) + if (ImportedSnapins.TryGetValue(psSnapinName, out PSSnapInInfo importedSnapin)) { - if (loadedSnapins == null) - { - loadedSnapins = new List(); - } - - loadedSnapins.Add(importedSnapin); + return importedSnapin; } - return loadedSnapins; + return null; } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Reflection.Assembly.LoadFrom")] @@ -4044,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) { @@ -4100,7 +4025,7 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) /// /// This is the default function to use for tab expansion. /// - private static string s_tabExpansionFunctionText = @" + private static readonly string s_tabExpansionFunctionText = @" <# Options include: RelativeFilePaths - [bool] Always resolve file paths using Resolve-Path -Relative. @@ -4112,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)] @@ -4151,7 +4078,7 @@ internal void ImportCmdletsFromAssembly(Assembly assembly, PSModuleInfo module) <#options#> $options) } } - "; +"; /// /// This is the default function to use for clear-host. @@ -4186,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 @@ -4269,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. @@ -4315,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 @@ -4330,7 +4372,30 @@ .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() { @@ -4435,147 +4500,199 @@ .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 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))), + + // Variable which controls the encoding for decoding data from a NativeCommand + new SessionStateVariableEntry( + SpecialVariables.PSApplicationOutputEncoding, + null, + RunspaceInit.PSApplicationOutputEncodingDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(Encoding))), + + // Preferences + // + // NTRAID#Windows Out Of Band Releases-931461-2006/03/13 + // 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(); + } + + /// + /// 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 everytime it's called. This + /// 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. /// @@ -4593,7 +4710,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), @@ -4624,7 +4741,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), @@ -4760,6 +4877,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(); } } @@ -4774,19 +4893,24 @@ internal static SessionStateAliasEntry[] BuiltInAliases internal static readonly ScriptBlock SetDriveScriptBlock = ScriptBlock.CreateDelayParsedScriptBlock(DefaultSetDriveFunctionText, isProductCode: true); - private static PSLanguageMode systemLanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage; + private static readonly PSLanguageMode systemLanguageMode = (SystemPolicy.GetSystemLockdownPolicy() == SystemEnforcementMode.Enforce) ? PSLanguageMode.ConstrainedLanguage : PSLanguageMode.FullLanguage; internal static readonly SessionStateFunctionEntry[] BuiltInFunctions = new SessionStateFunctionEntry[] { // 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), @@ -4841,11 +4965,10 @@ internal static void RemoveAllDrivesForProvider(ProviderInfo pi, SessionStateInt } } - private static PSTraceSource s_PSSnapInTracer = PSTraceSource.GetTracer("PSSnapInLoadUnload", "Loading and unloading mshsnapins", false); + private static readonly PSTraceSource s_PSSnapInTracer = PSTraceSource.GetTracer("PSSnapInLoadUnload", "Loading and unloading mshsnapins", false); 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) @@ -4994,10 +5117,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; @@ -5244,12 +5365,12 @@ private static void AnalyzeModuleAssemblyWithReflection( cmdlet.SetModule(moduleInfo); } - cmdlets = cmdlets ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + cmdlets ??= new Dictionary(StringComparer.OrdinalIgnoreCase); cmdlets.Add(cmdletName, cmdlet); if (TryGetCustomAttribute(type, out AliasAttribute aliasAttribute)) { - aliases = aliases ?? new Dictionary>(StringComparer.OrdinalIgnoreCase); + aliases ??= new Dictionary>(StringComparer.OrdinalIgnoreCase); var aliasList = new List(); foreach (var alias in aliasAttribute.AliasNames) @@ -5258,7 +5379,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) { @@ -5302,7 +5427,7 @@ private static void AnalyzeModuleAssemblyWithReflection( provider.SetModule(moduleInfo); } - providers = providers ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + providers ??= new Dictionary(StringComparer.OrdinalIgnoreCase); providers.Add(providerName, provider); s_PSSnapInTracer.WriteLine("{0} from type {1} is added as a provider. ", providerName, type.FullName); @@ -5329,7 +5454,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) }, @@ -5354,6 +5478,7 @@ private static void InitializeCoreCmdletsAndProviders( { "Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, { "Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, { "Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), helpFile) }, + { "Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile) }, { "Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) }, { "Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) }, { "Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, @@ -5361,6 +5486,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) }, @@ -5392,6 +5518,11 @@ private static void InitializeCoreCmdletsAndProviders( { "Out-LineOutput", new SessionStateCmdletEntry("Out-LineOutput", typeof(OutLineOutputCommand), helpFile) }, { "Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, }; + +#if UNIX + cmdlets.Add("Switch-Process", new SessionStateCmdletEntry("Switch-Process", typeof(SwitchProcessCommand), helpFile)); +#endif + foreach (var val in cmdlets.Values) { val.SetPSSnapIn(psSnapInInfo); @@ -5408,6 +5539,7 @@ private static void InitializeCoreCmdletsAndProviders( { "Function", new SessionStateProviderEntry("Function", typeof(FunctionProvider), helpFile) }, { "Variable", new SessionStateProviderEntry("Variable", typeof(VariableProvider), helpFile) }, }; + foreach (var val in providers.Values) { val.SetPSSnapIn(psSnapInInfo); @@ -5432,7 +5564,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) { @@ -5475,7 +5607,7 @@ private static bool IsProviderClass(Type type) [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasDefaultConstructor(Type type) { - return !(type.GetConstructor(Type.EmptyTypes) == null); + return type.GetConstructor(Type.EmptyTypes) is not null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -5485,7 +5617,7 @@ private static string GetHelpFile(string assemblyPath) return Path.GetFileName(assemblyPath).Replace(".ni.dll", ".dll") + StringLiterals.HelpFileExtension; } - private static PSTraceSource s_PSSnapInTracer = PSTraceSource.GetTracer("PSSnapInLoadUnload", "Loading and unloading mshsnapins", false); + private static readonly PSTraceSource s_PSSnapInTracer = PSTraceSource.GetTracer("PSSnapInLoadUnload", "Loading and unloading mshsnapins", false); } // Guid is {15d4c170-2f29-5689-a0e2-d95b0c7b4ea0} diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 7c37cc4f0eb..4e50e2d130f 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 { @@ -80,9 +82,9 @@ public sealed class ForEachObjectCommand : PSCmdlet, IDisposable [Parameter(ValueFromPipeline = true, ParameterSetName = ForEachObjectCommand.ParallelParameterSet)] public PSObject InputObject { - set { _inputObject = value; } - get { return _inputObject; } + + set { _inputObject = value; } } private PSObject _inputObject = AutomationNull.Value; @@ -91,7 +93,7 @@ public PSObject InputObject #region ScriptBlockSet - private List _scripts = new List(); + private readonly List _scripts = new List(); /// /// Gets or sets the script block to apply in begin processing. @@ -220,9 +222,9 @@ public string MemberName [Alias("Args")] public object[] ArgumentList { - set { _arguments = value; } - get { return _arguments; } + + set { _arguments = value; } } private object[] _arguments; @@ -384,10 +386,11 @@ public void Dispose() 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( @@ -407,19 +410,18 @@ private void InitParallelParameterSet() { } - bool allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; - _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesAsDictionary( - Parallel, - allowUsingExpression, - this.Context, - null); + var allowUsingExpression = this.Context.SessionState.LanguageMode != PSLanguageMode.NoLanguage; + _usingValuesMap = ScriptBlockToPowerShellConverter.GetUsingValuesForEachParallel( + scriptBlock: Parallel, + isTrustedInput: allowUsingExpression, + 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( @@ -455,10 +457,7 @@ private void InitParallelParameterSet() _taskCollection = new PSDataCollection(); _taskDataStreamWriter = new PSTaskDataStreamWriter(this); _taskPool = new PSTaskPool(ThrottleLimit, UseNewRunspace); - _taskPool.PoolComplete += (sender, args) => - { - _taskDataStreamWriter.Close(); - }; + _taskPool.PoolComplete += (sender, args) => _taskDataStreamWriter.Close(); // Create timeout timer if requested. if (TimeoutSeconds != 0) @@ -687,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)) @@ -703,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, @@ -922,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()); } } @@ -1024,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( @@ -1035,7 +1034,7 @@ private void MethodCallWithArguments() _propertyOrMethodName, possibleMatches)); } - else if (methods.Count == 0 || !(methods[0] is PSMethodInfo)) + else if (methods.Count == 0 || methods[0] is not PSMethodInfo) { // write error record: method no found WriteError(GenerateNameParameterError( @@ -1054,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, @@ -1207,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); } } @@ -1235,7 +1245,7 @@ private bool BlockMethodInLanguageMode(object inputObject) internal static ErrorRecord GenerateNameParameterError(string paraName, string resourceString, string errorId, object target, params object[] args) { string message; - if (args == null || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -1431,8 +1441,11 @@ public SwitchParameter EQ set { - _binaryOperator = TokenKind.Ieq; - _forceBooleanEvaluation = false; + if (value) + { + _binaryOperator = TokenKind.Ieq; + _forceBooleanEvaluation = false; + } } } @@ -1449,7 +1462,10 @@ public SwitchParameter CEQ set { - _binaryOperator = TokenKind.Ceq; + if (value) + { + _binaryOperator = TokenKind.Ceq; + } } } @@ -1467,7 +1483,10 @@ public SwitchParameter NE set { - _binaryOperator = TokenKind.Ine; + if (value) + { + _binaryOperator = TokenKind.Ine; + } } } @@ -1484,7 +1503,10 @@ public SwitchParameter CNE set { - _binaryOperator = TokenKind.Cne; + if (value) + { + _binaryOperator = TokenKind.Cne; + } } } @@ -1502,7 +1524,10 @@ public SwitchParameter GT set { - _binaryOperator = TokenKind.Igt; + if (value) + { + _binaryOperator = TokenKind.Igt; + } } } @@ -1519,7 +1544,10 @@ public SwitchParameter CGT set { - _binaryOperator = TokenKind.Cgt; + if (value) + { + _binaryOperator = TokenKind.Cgt; + } } } @@ -1537,12 +1565,15 @@ public SwitchParameter LT set { - _binaryOperator = TokenKind.Ilt; + if (value) + { + _binaryOperator = TokenKind.Ilt; + } } } /// - /// Gets -sets case sensitive binary operator -clt. + /// Gets or sets case sensitive binary operator -clt. /// [Parameter(Mandatory = true, ParameterSetName = "CaseSensitiveLessThanSet")] public SwitchParameter CLT @@ -1554,7 +1585,10 @@ public SwitchParameter CLT set { - _binaryOperator = TokenKind.Clt; + if (value) + { + _binaryOperator = TokenKind.Clt; + } } } @@ -1572,7 +1606,10 @@ public SwitchParameter GE set { - _binaryOperator = TokenKind.Ige; + if (value) + { + _binaryOperator = TokenKind.Ige; + } } } @@ -1589,7 +1626,10 @@ public SwitchParameter CGE set { - _binaryOperator = TokenKind.Cge; + if (value) + { + _binaryOperator = TokenKind.Cge; + } } } @@ -1607,7 +1647,10 @@ public SwitchParameter LE set { - _binaryOperator = TokenKind.Ile; + if (value) + { + _binaryOperator = TokenKind.Ile; + } } } @@ -1624,7 +1667,10 @@ public SwitchParameter CLE set { - _binaryOperator = TokenKind.Cle; + if (value) + { + _binaryOperator = TokenKind.Cle; + } } } @@ -1642,7 +1688,10 @@ public SwitchParameter Like set { - _binaryOperator = TokenKind.Ilike; + if (value) + { + _binaryOperator = TokenKind.Ilike; + } } } @@ -1659,7 +1708,10 @@ public SwitchParameter CLike set { - _binaryOperator = TokenKind.Clike; + if (value) + { + _binaryOperator = TokenKind.Clike; + } } } @@ -1677,7 +1729,10 @@ public SwitchParameter NotLike set { - _binaryOperator = TokenKind.Inotlike; + if (value) + { + _binaryOperator = TokenKind.Inotlike; + } } } @@ -1694,7 +1749,10 @@ public SwitchParameter CNotLike set { - _binaryOperator = TokenKind.Cnotlike; + if (value) + { + _binaryOperator = TokenKind.Cnotlike; + } } } @@ -1712,7 +1770,10 @@ public SwitchParameter Match set { - _binaryOperator = TokenKind.Imatch; + if (value) + { + _binaryOperator = TokenKind.Imatch; + } } } @@ -1729,7 +1790,10 @@ public SwitchParameter CMatch set { - _binaryOperator = TokenKind.Cmatch; + if (value) + { + _binaryOperator = TokenKind.Cmatch; + } } } @@ -1747,7 +1811,10 @@ public SwitchParameter NotMatch set { - _binaryOperator = TokenKind.Inotmatch; + if (value) + { + _binaryOperator = TokenKind.Inotmatch; + } } } @@ -1764,7 +1831,10 @@ public SwitchParameter CNotMatch set { - _binaryOperator = TokenKind.Cnotmatch; + if (value) + { + _binaryOperator = TokenKind.Cnotmatch; + } } } @@ -1782,7 +1852,10 @@ public SwitchParameter Contains set { - _binaryOperator = TokenKind.Icontains; + if (value) + { + _binaryOperator = TokenKind.Icontains; + } } } @@ -1799,7 +1872,10 @@ public SwitchParameter CContains set { - _binaryOperator = TokenKind.Ccontains; + if (value) + { + _binaryOperator = TokenKind.Ccontains; + } } } @@ -1817,7 +1893,10 @@ public SwitchParameter NotContains set { - _binaryOperator = TokenKind.Inotcontains; + if (value) + { + _binaryOperator = TokenKind.Inotcontains; + } } } @@ -1834,7 +1913,10 @@ public SwitchParameter CNotContains set { - _binaryOperator = TokenKind.Cnotcontains; + if (value) + { + _binaryOperator = TokenKind.Cnotcontains; + } } } @@ -1852,7 +1934,10 @@ public SwitchParameter In set { - _binaryOperator = TokenKind.In; + if (value) + { + _binaryOperator = TokenKind.In; + } } } @@ -1869,7 +1954,10 @@ public SwitchParameter CIn set { - _binaryOperator = TokenKind.Cin; + if (value) + { + _binaryOperator = TokenKind.Cin; + } } } @@ -1887,7 +1975,10 @@ public SwitchParameter NotIn set { - _binaryOperator = TokenKind.Inotin; + if (value) + { + _binaryOperator = TokenKind.Inotin; + } } } @@ -1904,7 +1995,10 @@ public SwitchParameter CNotIn set { - _binaryOperator = TokenKind.Cnotin; + if (value) + { + _binaryOperator = TokenKind.Cnotin; + } } } @@ -1921,7 +2015,10 @@ public SwitchParameter Is set { - _binaryOperator = TokenKind.Is; + if (value) + { + _binaryOperator = TokenKind.Is; + } } } @@ -1938,7 +2035,10 @@ public SwitchParameter IsNot set { - _binaryOperator = TokenKind.IsNot; + if (value) + { + _binaryOperator = TokenKind.IsNot; + } } } @@ -1955,7 +2055,10 @@ public SwitchParameter Not set { - _binaryOperator = TokenKind.Not; + if (value) + { + _binaryOperator = TokenKind.Not; + } } } @@ -2008,8 +2111,7 @@ private void CheckLanguageMode() private object GetLikeRHSOperand(object operand) { - var val = operand as string; - if (val == null) + if (operand is not string val) { return operand; } @@ -2361,7 +2463,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( @@ -2640,46 +2742,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 (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) + if (string.Equals("latest", versionString, StringComparison.OrdinalIgnoreCase)) { - // 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); } } @@ -2688,7 +2763,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( @@ -2704,7 +2779,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 @@ -2735,6 +2811,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/NetApiBufferFree.cs b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs new file mode 100644 index 00000000000..4f7d0541872 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + + [LibraryImport("Netapi32.dll")] + internal static partial uint NetApiBufferFree(nint Buffer); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/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..d23899e8ef7 --- /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 = [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..01667fb4274 --- /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 = [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 1871ae3c9cd..402d7c8d4a6 100644 --- a/src/System.Management.Automation/engine/InvocationInfo.cs +++ b/src/System.Management.Automation/engine/InvocationInfo.cs @@ -201,11 +201,13 @@ public Dictionary BoundParameters { get { - return _boundParameters ?? - (_boundParameters = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return _boundParameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } - internal set { _boundParameters = value; } + internal set + { + _boundParameters = value; + } } /// @@ -213,7 +215,7 @@ public Dictionary BoundParameters /// public List UnboundArguments { - get { return _unboundArguments ?? (_unboundArguments = new List()); } + get { return _unboundArguments ??= new List(); } internal set { _unboundArguments = value; } } @@ -269,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. @@ -382,7 +396,10 @@ internal IScriptExtent ScriptPosition } } - set { _scriptPosition = value; } + set + { + _scriptPosition = value; + } } /// @@ -435,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); @@ -518,4 +535,3 @@ public override ReadOnlyCollection OutputType private string _definition; } } - diff --git a/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs index 699d60dafa0..1809ebd1c18 100644 --- a/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ItemCmdletProviderInterfaces.cs @@ -2082,8 +2082,8 @@ internal bool IsContainer( #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data } @@ -2105,4 +2105,3 @@ public enum CopyContainers CopyChildrenOfTargetContainer } } - diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 43c21f512c1..13d8f66e5e9 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. @@ -299,7 +298,7 @@ internal enum ConversionRank public static class LanguagePrimitives { [TraceSource("ETS", "Extended Type System")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("ETS", "Extended Type System"); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ETS", "Extended Type System"); internal delegate void MemberNotFoundError(PSObject pso, DictionaryEntry property, Type resultType); @@ -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,10 +364,10 @@ 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 object _enumerable; - private Type _enumerableType; + private readonly object _enumerable; + private readonly Type _enumerableType; private DynamicMethod _getEnumerator; internal EnumerableTWrapper(object enumerable, Type enumerableType) @@ -384,7 +386,7 @@ private void CreateGetEnumerator() emitter.Emit(OpCodes.Ldarg_0); emitter.Emit(OpCodes.Castclass, _enumerableType); - MethodInfo methodInfo = _enumerableType.GetMethod("GetEnumerator", new Type[] { }); + MethodInfo methodInfo = _enumerableType.GetMethod("GetEnumerator", Array.Empty()); emitter.Emit(OpCodes.Callvirt, methodInfo); emitter.Emit(OpCodes.Ret); } @@ -414,7 +416,7 @@ private static IEnumerable GetEnumerableFromIEnumerableT(object obj) private delegate IEnumerable GetEnumerableDelegate(object obj); - private static Dictionary s_getEnumerableCache = new Dictionary(32); + private static readonly Dictionary s_getEnumerableCache = new Dictionary(32); private static GetEnumerableDelegate GetOrCalculateEnumerable(Type type) { @@ -632,13 +634,9 @@ 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; - var culture = formatProvider as CultureInfo; - if (culture == null) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -780,13 +778,9 @@ 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; - var culture = formatProvider as CultureInfo; - if (culture == null) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -901,12 +895,9 @@ 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 CultureInfo culture)) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -936,7 +927,7 @@ public static bool TryCompare(object first, object second, bool ignoreCase, IFor if (first is string firstString) { - if (!(second is string secondString)) + if (second is not string secondString) { if (!TryConvertTo(second, culture, out secondString)) { @@ -1045,9 +1036,7 @@ internal static bool IsTrue(IList objectArray) // but since we don't want this to recurse indefinitely // we explicitly check the case where it would recurse // and deal with it. - IList firstElement = PSObject.Base(objectArray[0]) as IList; - - if (firstElement == null) + if (PSObject.Base(objectArray[0]) is not IList firstElement) { return IsTrue(objectArray[0]); } @@ -1506,7 +1495,7 @@ internal static object GetConverter(Type type, TypeTable backupTypeTable) private static object NewConverterInstance(string assemblyQualifiedTypeName) { - if (assemblyQualifiedTypeName.IndexOf(',') == -1) + if (!assemblyQualifiedTypeName.Contains(',')) { typeConversion.WriteLine("Type name \"{0}\" should be assembly qualified.", assemblyQualifiedTypeName); return null; @@ -1579,7 +1568,7 @@ public static string ConvertTypeNameToPSTypeName(string typeName) // CIM name string to .NET namestring mapping table // (Considered using the MI routines but they didn't do quite the right thing. // - private static Dictionary s_nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { + private static readonly Dictionary s_nameMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SInt8", "SByte" }, { "UInt8", "Byte" }, { "SInt16", "Int16" }, @@ -1599,8 +1588,8 @@ public static string ConvertTypeNameToPSTypeName(string typeName) { "BooleanArray", "bool[]" }, { "UInt8Array", "byte[]" }, { "SInt8Array", "Sbyte[]" }, - { "UInt16Array", "uint16[]" }, - { "SInt16Array", "int64[]" }, + { "UInt16Array", "UInt16[]" }, + { "SInt16Array", "Int16[]" }, { "UInt32Array", "UInt32[]" }, { "SInt32Array", "Int32[]" }, { "UInt64Array", "UInt64[]" }, @@ -1893,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) { @@ -2087,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); @@ -2095,8 +2100,7 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo protected static object BaseConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase, bool multipleValues) { Diagnostics.Assert(sourceValue != null, "the type converter has a special case for null source values"); - string sourceValueString = sourceValue as string; - if (sourceValueString == null) + if (sourceValue is not string sourceValueString) { throw new PSInvalidCastException("InvalidCastEnumFromTypeNotAString", null, ExtendedTypeSystem.InvalidCastException, @@ -2138,7 +2142,7 @@ protected static object BaseConvertFrom(object sourceValue, Type destinationType WildcardPattern[] fromValuePatterns; if (!multipleValues) { - if (sourceValueString.Contains(",")) + if (sourceValueString.Contains(',')) { throw new PSInvalidCastException("InvalidCastEnumCommaAndNoFlags", null, ExtendedTypeSystem.InvalidCastExceptionEnumerationNoFlagAndComma, @@ -2158,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++) { @@ -2879,8 +2883,16 @@ private static bool TryScanNumber(string strToConvert, Type resultType, out obje { try { + var parsedNumber = Parser.ScanNumber(strToConvert, resultType, shouldTryCoercion: false); + if (resultType == typeof(BigInteger) || parsedNumber is BigInteger) + { + // Convert.ChangeType() cannot be used here as BigInteger is not IConvertible. + result = ConvertTo(parsedNumber, resultType); + return true; + } + result = Convert.ChangeType( - Parser.ScanNumber(strToConvert, resultType, shouldTryCoercion: false), + parsedNumber, resultType, System.Globalization.CultureInfo.InvariantCulture.NumberFormat); return true; @@ -2893,41 +2905,56 @@ private static bool TryScanNumber(string strToConvert, Type resultType, out obje } } - private static object ConvertStringToInteger(object valueToConvert, - Type resultType, - bool recursion, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) + private static object ConvertStringToInteger( + object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) { var strToConvert = valueToConvert as string; Diagnostics.Assert(strToConvert != null, "Value to convert must be a string"); - Diagnostics.Assert(IsNumeric(GetTypeCode(resultType)), "Result type must be numeric"); + Diagnostics.Assert( + IsNumeric(GetTypeCode(resultType)) || resultType == typeof(BigInteger), + "Result type must be numeric"); if (strToConvert.Length == 0) { typeConversion.WriteLine("Returning numeric zero."); + + // BigInteger is not IConvertible and will throw from ChangeType; we know the value we're after is zero. + if (resultType == typeof(BigInteger)) + { + return BigInteger.Zero; + } + // This is not wrapped in a try/catch because it can't fail. - return System.Convert.ChangeType(0, resultType, CultureInfo.InvariantCulture); + return System.Convert.ChangeType(value: 0, resultType, CultureInfo.InvariantCulture); } typeConversion.WriteLine("Converting to integer."); - TypeConverter integerConverter = LanguagePrimitives.GetIntegerSystemConverter(resultType); + try { if (TryScanNumber(strToConvert, resultType, out object result)) { return result; } - else - { - return integerConverter.ConvertFrom(strToConvert); + + 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); } catch (Exception e) { // This catch has one extra reason to be generic (Exception e). - // integerConverter.ConvertFrom warps its exceptions in a System.Exception. + // TypeConverter.ConvertFrom wraps its exceptions in a System.Exception. if (e.InnerException != null) { e = e.InnerException; @@ -3220,6 +3247,24 @@ private static bool ConvertDecimalToBool(object valueToConvert, return ((Decimal)valueToConvert) != default(Decimal); } + private static bool ConvertBigIntegerToBool( + object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) + => ((BigInteger)valueToConvert) != BigInteger.Zero; + + private static object ConvertBoolToBigInteger( + object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) + => (bool)valueToConvert ? BigInteger.One : BigInteger.Zero; + private static PSConverter CreateNumericToBoolConverter(Type fromType) { Diagnostics.Assert(LanguagePrimitives.IsNumeric(fromType.GetTypeCode()), "Can only convert numeric types"); @@ -3643,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; @@ -3703,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; @@ -3769,7 +3814,7 @@ internal object ConvertWithoutCulture(object valueToConvert, } } - private class ConvertViaConstructor + private sealed class ConvertViaConstructor { internal Func TargetCtorLambda; @@ -3808,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; @@ -3906,7 +3951,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaNoArgumentConstructor + private sealed class ConvertViaNoArgumentConstructor { private readonly Func _constructor; @@ -3943,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) @@ -4009,7 +4068,7 @@ internal object Convert(object valueToConvert, } } - private class ConvertViaCast + private sealed class ConvertViaCast { internal MethodInfo cast; @@ -4089,7 +4148,7 @@ private static object ConvertNumericIConvertible(object valueToConvert, } } - private class ConvertCheckingForCustomConverter + private sealed class ConvertCheckingForCustomConverter { internal PSConverter tryfirstConverter; internal PSConverter fallbackConverter; @@ -4132,14 +4191,22 @@ internal object Convert(object valueToConvert, } #region Delegates converting null - private static object ConvertNullToNumeric(object valueToConvert, - Type resultType, - bool recursion, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable) + private static object ConvertNullToNumeric( + object valueToConvert, + Type resultType, + bool recursion, + PSObject originalValueToConvert, + IFormatProvider formatProvider, + TypeTable backupTable) { typeConversion.WriteLine("Converting null to zero."); + + // Handle BigInteger first, as it is not IConvertible + if (resultType == typeof(BigInteger)) + { + return BigInteger.Zero; + } + // If the destination type is numeric, convert 0 to resultType return System.Convert.ChangeType(0, resultType, CultureInfo.InvariantCulture); } @@ -4283,7 +4350,7 @@ public override int GetHashCode() public override bool Equals(object other) { - if (!(other is ConversionTypePair)) + if (other is not ConversionTypePair) return false; var ctp = (ConversionTypePair)other; @@ -4300,19 +4367,21 @@ internal delegate T PSConverter(object valueToConvert, internal delegate object PSNullConverter(object nullOrAutomationNull); +#nullable enable internal interface IConversionData { object Converter { get; } ConversionRank Rank { get; } - object Invoke(object valueToConvert, + object? Invoke(object? valueToConvert, Type resultType, bool recurse, - PSObject originalValueToConvert, - IFormatProvider formatProvider, - TypeTable backupTable); + PSObject? originalValueToConvert, + IFormatProvider? formatProvider, + TypeTable? backupTable); } +#nullable restore [System.Diagnostics.DebuggerDisplay("{_converter.Method.Name}")] internal class ConversionData : IConversionData @@ -4338,7 +4407,7 @@ public object Invoke(object valueToConvert, Type resultType, bool recurse, PSObj } } - private static Dictionary s_converterCache = new Dictionary(256); + private static readonly Dictionary s_converterCache = new Dictionary(256); private static IConversionData CacheConversion(Type fromType, Type toType, PSConverter converter, ConversionRank rank) { @@ -4376,7 +4445,7 @@ internal static ConversionRank GetConversionRank(Type fromType, Type toType) return FigureConversion(fromType, toType).Rank; } - private static Type[] s_numericTypes = new Type[] { + private static readonly Type[] s_numericTypes = new Type[] { typeof(Int16), typeof(Int32), typeof(Int64), typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(sbyte), typeof(byte), @@ -4384,17 +4453,17 @@ internal static ConversionRank GetConversionRank(Type fromType, Type toType) typeof(BigInteger) }; - private static Type[] s_integerTypes = new Type[] { + private static readonly Type[] s_integerTypes = new Type[] { typeof(Int16), typeof(Int32), typeof(Int64), typeof(UInt16), typeof(UInt32), typeof(UInt64), typeof(sbyte), typeof(byte) }; // Do not reorder the elements of these arrays, we depend on them being ordered by increasing size. - private static Type[] s_signedIntegerTypes = new Type[] { typeof(sbyte), typeof(Int16), typeof(Int32), typeof(Int64) }; - private static Type[] s_unsignedIntegerTypes = new Type[] { typeof(byte), typeof(UInt16), typeof(UInt32), typeof(UInt64) }; + private static readonly Type[] s_signedIntegerTypes = new Type[] { typeof(sbyte), typeof(Int16), typeof(Int32), typeof(Int64) }; + private static readonly Type[] s_unsignedIntegerTypes = new Type[] { typeof(byte), typeof(UInt16), typeof(UInt32), typeof(UInt64) }; - private static Type[] s_realTypes = new Type[] { typeof(Single), typeof(double), typeof(decimal) }; + private static readonly Type[] s_realTypes = new Type[] { typeof(Single), typeof(double), typeof(decimal) }; internal static void RebuildConversionCache() { @@ -4416,6 +4485,8 @@ internal static void RebuildConversionCache() CacheConversion(typeofNull, type, LanguagePrimitives.ConvertNullToNumeric, ConversionRank.NullToValue); } + CacheConversion(typeofBool, typeof(BigInteger), ConvertBoolToBigInteger, ConversionRank.Language); + CacheConversion(typeof(Int16), typeofBool, ConvertInt16ToBool, ConversionRank.Language); CacheConversion(typeof(Int32), typeofBool, ConvertInt32ToBool, ConversionRank.Language); CacheConversion(typeof(Int64), typeofBool, ConvertInt64ToBool, ConversionRank.Language); @@ -4427,6 +4498,7 @@ internal static void RebuildConversionCache() CacheConversion(typeof(Single), typeofBool, ConvertSingleToBool, ConversionRank.Language); CacheConversion(typeof(double), typeofBool, ConvertDoubleToBool, ConversionRank.Language); CacheConversion(typeof(decimal), typeofBool, ConvertDecimalToBool, ConversionRank.Language); + CacheConversion(typeof(BigInteger), typeofBool, ConvertBigIntegerToBool, ConversionRank.Language); for (int i = 0; i < LanguagePrimitives.s_unsignedIntegerTypes.Length; i++) { @@ -4481,6 +4553,8 @@ internal static void RebuildConversionCache() } } + CacheConversion(typeofString, typeof(BigInteger), ConvertStringToInteger, ConversionRank.NumericString); + CacheConversion(typeofFloat, typeofDouble, LanguagePrimitives.ConvertNumeric, ConversionRank.NumericImplicit); CacheConversion(typeofDouble, typeofFloat, LanguagePrimitives.ConvertNumeric, ConversionRank.NumericExplicit); CacheConversion(typeofFloat, typeofDecimal, LanguagePrimitives.ConvertNumeric, ConversionRank.NumericExplicit); @@ -4617,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 { @@ -4642,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 @@ -4679,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 == false) + if (!first) { - availableProperties.Append(" , "); + availableProperties.Append(", "); } availableProperties.Append("[" + p.Name + " <" + p.TypeNameOfValue + ">]"); @@ -4838,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); } @@ -5050,7 +5153,7 @@ private static IConversionData FigureLanguageConversion(Type fromType, Type toTy return null; } - private struct SignatureComparator + private readonly struct SignatureComparator { private enum TypeMatchingContext { @@ -5521,7 +5624,7 @@ private static bool TypeConverterPossiblyExists(Type type) return false; } - private static Dictionary s_possibleTypeConverter = new Dictionary(16); + private static readonly Dictionary s_possibleTypeConverter = new Dictionary(16); // This is the internal dummy type used when an IDictionary is converted to a pscustomobject // PS C:\> $ps = [pscustomobject]@{a=10;b=5} @@ -5577,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); @@ -5689,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)) @@ -5725,7 +5825,7 @@ internal static IConversionData FigureConversion(Type fromType, Type toType) return CacheConversion(fromType, toType, converter, rank); } - internal class Null { }; + internal class Null { } private static IConversionData FigureConversionFromNull(Type toType) { diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index 94e07d3af0e..9b69ee168d7 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -77,17 +77,17 @@ public WMIParameterInformation(string name, Type ty) : base(ty, true, null, fals /// /// /// - private IEnumerable GetTypeNameHierarchyFromDerivation(ManagementBaseObject managementObj, + private static IEnumerable GetTypeNameHierarchyFromDerivation(ManagementBaseObject managementObj, string dotnetBaseType, bool shouldIncludeNamespace) { StringBuilder type = new StringBuilder(200); // give the typename based on NameSpace and Class type.Append(dotnetBaseType); - type.Append("#"); + type.Append('#'); if (shouldIncludeNamespace) { type.Append(managementObj.SystemProperties["__NAMESPACE"].Value); - type.Append("\\"); + type.Append('\\'); } type.Append(managementObj.SystemProperties["__CLASS"].Value); @@ -112,11 +112,11 @@ private IEnumerable GetTypeNameHierarchyFromDerivation(ManagementBaseObj { type.Clear(); type.Append(dotnetBaseType); - type.Append("#"); + type.Append('#'); if (shouldIncludeNamespace) { type.Append(managementObj.SystemProperties["__NAMESPACE"].Value); - type.Append("\\"); + type.Append('\\'); } type.Append(t); @@ -172,9 +172,7 @@ protected override T GetMember(object obj, string memberName) { tracer.WriteLine("Getting member with name {0}", memberName); - ManagementBaseObject mgmtObject = obj as ManagementBaseObject; - - if (mgmtObject == null) + if (obj is not ManagementBaseObject mgmtObject) { return null; } @@ -366,8 +364,7 @@ protected override object PropertyGet(PSProperty property) /// Instructs the adapter to convert before setting, if the adapter supports conversion. protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible) { - ManagementBaseObject mObj = property.baseObject as ManagementBaseObject; - if (mObj == null) + if (property.baseObject is not ManagementBaseObject mObj) { throw new SetValueInvocationException("CannotSetNonManagementObjectMsg", null, @@ -412,7 +409,7 @@ protected override string PropertyToString(PSProperty property) // } returnValue.Append(PropertyType(property, forDisplay: true)); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(property.Name); returnValue.Append(" {"); if (PropertyIsGettable(property)) @@ -425,7 +422,7 @@ protected override string PropertyToString(PSProperty property) returnValue.Append("set;"); } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -457,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) @@ -580,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) { @@ -856,7 +856,7 @@ internal static string GetMethodDefinition(MethodData mData) } inParameterString.Append(typeName); - inParameterString.Append(" "); + inParameterString.Append(' '); inParameterString.Append(parameter.Name); inParameterString.Append(", "); } @@ -872,9 +872,9 @@ internal static string GetMethodDefinition(MethodData mData) builder.Append("System.Management.ManagementBaseObject "); builder.Append(mData.Name); - builder.Append("("); - builder.Append(inParameterString.ToString()); - builder.Append(")"); + builder.Append('('); + builder.Append(inParameterString); + builder.Append(')'); string returnValue = builder.ToString(); tracer.WriteLine("Definition constructed: {0}", returnValue); @@ -946,7 +946,7 @@ protected abstract PSProperty DoGetProperty(ManagementBaseObject wmiObject, #region Private Data - private static HybridDictionary s_instanceMethodCacheTable = new HybridDictionary(); + private static readonly HybridDictionary s_instanceMethodCacheTable = new HybridDictionary(); #endregion } @@ -1165,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 9efd502badb..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 = @@ -657,12 +664,12 @@ internal MergedCompiledCommandParameter( /// /// Gets the compiled command parameter for the association. /// - internal CompiledCommandParameter Parameter { get; private set; } + internal CompiledCommandParameter Parameter { get; } /// /// Gets the type of binder that the compiled command parameter should be bound with. /// - internal ParameterBinderAssociation BinderAssociation { get; private set; } + internal ParameterBinderAssociation BinderAssociation { get; } public override string ToString() { @@ -708,4 +715,3 @@ internal enum ParameterBinderAssociation PagingParameters, } } - diff --git a/src/System.Management.Automation/engine/MinishellParameterBinderController.cs b/src/System.Management.Automation/engine/MinishellParameterBinderController.cs index a85b462abb9..e19defd57a7 100644 --- a/src/System.Management.Automation/engine/MinishellParameterBinderController.cs +++ b/src/System.Management.Automation/engine/MinishellParameterBinderController.cs @@ -37,24 +37,6 @@ internal MinishellParameterBinderController( #endregion ctor - /// - /// Override of parent class which should not be used. - /// - /// - /// The parameters to bind. - /// - /// - /// For any parameters that do not have a name, they are added to the command - /// line arguments for the command - /// - internal override - Collection - BindParameters(Collection parameters) - { - Dbg.Assert(false, "this method should be used"); - return null; - } - /// /// Value of input format. This property should be read after binding of parameters. /// @@ -117,7 +99,7 @@ internal Collection BindParameters(Collection /// Handles error handling if some parameter is specified more than once. @@ -289,7 +271,7 @@ private void HandleSeenParameter(ref MinishellParameters seen, MinishellParamete } else { - seen = seen | parameter; + seen |= parameter; } } diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index dd188b3ff99..8a5f29e2b16 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -27,19 +28,18 @@ namespace System.Management.Automation /// Changes to these type of modules will not be re-analyzed, unless the user re-imports the module, /// or runs Get-Module -List. /// - internal class AnalysisCache + internal static class AnalysisCache { - private static AnalysisCacheData s_cacheData = AnalysisCacheData.Get(); + private static readonly AnalysisCacheData s_cacheData = AnalysisCacheData.Get(); // This dictionary shouldn't see much use, so low concurrency and capacity - private static ConcurrentDictionary s_modulesBeingAnalyzed = - new ConcurrentDictionary( /*concurrency*/1, /*capacity*/2, StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_modulesBeingAnalyzed = + 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) { @@ -194,8 +194,7 @@ internal static bool ModuleIsEditionIncompatible(string modulePath, Hashtable mo internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bool hadCmdlets, bool hadFunctions, bool hadAliases) { - var modulePath = modulePathObj as string; - if (modulePath == null) + if (modulePathObj is not string modulePath) return true; if (modulePath.EndsWith(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase)) @@ -257,8 +256,7 @@ private static bool CheckModulesTypesInManifestAgainstExportedCommands(Hashtable return ModuleAnalysisViaGetModuleRequired(nestedModule, hadCmdlets, hadFunctions, hadAliases); } - var nestedModuleArray = nestedModules as object[]; - if (nestedModuleArray == null) + if (nestedModules is not object[] nestedModuleArray) return true; foreach (var element in nestedModuleArray) @@ -347,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; } @@ -359,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); } } @@ -373,11 +371,11 @@ private static ConcurrentDictionary AnalyzeScriptModule(st try { - foreach (string item in Directory.GetFiles(baseDirectory, "*.ps1")) + foreach (string item in Directory.EnumerateFiles(baseDirectory, "*.ps1")) { var command = Path.GetFileNameWithoutExtension(item); result.AddOrUpdate(command, CommandTypes.ExternalScript, - (_, existingCommandType) => existingCommandType | CommandTypes.ExternalScript); + static (_, existingCommandType) => existingCommandType | CommandTypes.ExternalScript); } } catch (UnauthorizedAccessException) @@ -386,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; @@ -642,7 +642,7 @@ private static bool GetModuleEntryFromCache(string modulePath, out DateTime last } } - internal class AnalysisCacheData + internal sealed class AnalysisCacheData { private static byte[] GetHeader() { @@ -664,6 +664,11 @@ private static byte[] GetHeader() public void QueueSerialization() { + if (string.IsNullOrEmpty(s_cacheStoreLocation)) + { + return; + } + // We expect many modules to rapidly call for serialization. // Instead of doing it right away, we'll queue a task that starts writing // after it seems like we've stopped adding stuff to write out. This is @@ -1092,7 +1097,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) { @@ -1118,10 +1123,10 @@ 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); + Platform.TryDeriveFromCache(cacheFileName, out s_cacheStoreLocation); } } diff --git a/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs b/src/System.Management.Automation/engine/Modules/ExportModuleMemberCommand.cs index 7dc91d76776..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. @@ -28,6 +29,11 @@ public sealed class ExportModuleMemberCommand : PSCmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Function { + get + { + return _functionList; + } + set { _functionList = value; @@ -42,8 +48,6 @@ public string[] Function } } } - - get { return _functionList; } } private string[] _functionList; @@ -57,6 +61,11 @@ public string[] Function [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Cmdlet { + get + { + return _cmdletList; + } + set { _cmdletList = value; @@ -71,8 +80,6 @@ public string[] Cmdlet } } } - - get { return _cmdletList; } } private string[] _cmdletList; @@ -86,6 +93,11 @@ public string[] Cmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Variable { + get + { + return _variableExportList; + } + set { _variableExportList = value; @@ -100,8 +112,6 @@ public string[] Variable } } } - - get { return _variableExportList; } } private string[] _variableExportList; @@ -115,6 +125,11 @@ public string[] Variable [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Alias { + get + { + return _aliasExportList; + } + set { _aliasExportList = value; @@ -129,8 +144,6 @@ public string[] Alias } } } - - get { return _aliasExportList; } } private string[] _aliasExportList; @@ -155,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 bfecce83e4e..caf7527d73e 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -165,8 +165,8 @@ private IEnumerable GetAvailableViaPsrpSessionCore(string[] module "Get-Module"); foreach ( PSObject outputObject in - RemoteDiscoveryHelper.InvokePowerShell(powerShell, this.CancellationToken, this, - errorMessageTemplate)) + RemoteDiscoveryHelper.InvokePowerShell(powerShell, this, errorMessageTemplate, + this.CancellationToken)) { PSModuleInfo moduleInfo = RemoteDiscoveryHelper.RehydratePSModuleInfo(outputObject); yield return moduleInfo; @@ -174,7 +174,7 @@ PSObject outputObject in } } - private PSModuleInfo GetModuleInfoForRemoteModuleWithoutManifest(RemoteDiscoveryHelper.CimModule cimModule) + private static PSModuleInfo GetModuleInfoForRemoteModuleWithoutManifest(RemoteDiscoveryHelper.CimModule cimModule) { return new PSModuleInfo(cimModule.ModuleName, null, null); } @@ -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; } @@ -297,29 +297,17 @@ protected override void StopProcessing() #region IDisposable Members /// - /// Releases resources associated with this object. + /// Release all resources. /// public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases resources associated with this object. - /// - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } - + _cancellationTokenSource.Dispose(); + _disposed = true; } @@ -382,8 +370,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 +503,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 +574,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 f6641930bf7..532079d1a77 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -63,9 +63,9 @@ public sealed class ImportModuleCommand : ModuleCmdletBase, IDisposable [Parameter] public SwitchParameter Global { - set { base.BaseGlobal = value; } - get { return base.BaseGlobal; } + + set { base.BaseGlobal = value; } } /// @@ -75,9 +75,9 @@ public SwitchParameter Global [ValidateNotNull] public string Prefix { - set { BasePrefix = value; } - get { return BasePrefix; } + + set { BasePrefix = value; } } /// @@ -89,7 +89,7 @@ public string Prefix [Parameter(ParameterSetName = ParameterSet_ViaWinCompat, Mandatory = true, ValueFromPipeline = true, Position = 0)] [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - public string[] Name { set; get; } = Array.Empty(); + public string[] Name { get; set; } = Array.Empty(); /// /// This parameter specifies the current pipeline object. @@ -117,6 +117,11 @@ public string Prefix [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Function { + get + { + return _functionImportList; + } + set { if (value == null) @@ -130,8 +135,6 @@ public string[] Function BaseFunctionPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _functionImportList; } } private string[] _functionImportList = Array.Empty(); @@ -144,6 +147,11 @@ public string[] Function [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Cmdlet { + get + { + return _cmdletImportList; + } + set { if (value == null) @@ -158,8 +166,6 @@ public string[] Cmdlet BaseCmdletPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _cmdletImportList; } } private string[] _cmdletImportList = Array.Empty(); @@ -172,6 +178,11 @@ public string[] Cmdlet [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Variable { + get + { + return _variableExportList; + } + set { if (value == null) @@ -185,8 +196,6 @@ public string[] Variable BaseVariablePatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _variableExportList; } } private string[] _variableExportList; @@ -199,6 +208,11 @@ public string[] Variable [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Alias { + get + { + return _aliasExportList; + } + set { if (value == null) @@ -213,8 +227,6 @@ public string[] Alias BaseAliasPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _aliasExportList; } } private string[] _aliasExportList; @@ -335,7 +347,7 @@ public Version RequiredVersion [Parameter(ParameterSetName = ParameterSet_ModuleInfo, Mandatory = true, ValueFromPipeline = true, Position = 0)] [ValidateTrustedData] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] - public PSModuleInfo[] ModuleInfo { set; get; } = Array.Empty(); + public PSModuleInfo[] ModuleInfo { get; set; } = Array.Empty(); /// /// The arguments to pass to the module script. @@ -375,7 +387,10 @@ public SwitchParameter DisableNameChecking [ValidateSet("Local", "Global")] public string Scope { - get { return _scope; } + get + { + return _scope; + } set { @@ -533,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) @@ -568,21 +566,40 @@ 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) { - bool found; - PSModuleInfo module = LoadBinaryModule(false, null, null, suppliedAssembly, null, null, + PSModuleInfo module = LoadBinaryModule( + parentModule: null, + moduleName: null, + fileName: null, + suppliedAssembly, + moduleBase: null, + ss: null, importModuleOptions, ManifestProcessingFlags.LoadElements | ManifestProcessingFlags.WriteErrors | ManifestProcessingFlags.NullOnFirstError, - this.BasePrefix, false /* loadTypes */ , false /* loadFormats */, out found); + 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); @@ -602,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 @@ -614,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; @@ -642,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)) { @@ -703,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)) { @@ -716,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); } } } @@ -740,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) @@ -764,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 @@ -785,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; @@ -834,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; @@ -852,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); } @@ -894,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; @@ -954,12 +995,12 @@ 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.CancellationToken, this, - errorMessageTemplate).ToList(); + errorMessageTemplate, + this.CancellationToken).ToList(); } List result = new List(); @@ -1076,8 +1117,7 @@ private PSModuleInfo ImportModule_RemotelyViaPsrpSession_SinglePreimportedModule CultureInfo.InvariantCulture, Modules.RemoteDiscoveryFailedToGenerateProxyForRemoteModule, remoteModuleName); - int numberOfLocallyCreatedFiles = RemoteDiscoveryHelper.InvokePowerShell(powerShell, this.CancellationToken, this, errorMessageTemplate).Count(); - if (numberOfLocallyCreatedFiles == 0) + if (!RemoteDiscoveryHelper.InvokePowerShell(powerShell, this, errorMessageTemplate, this.CancellationToken).Any()) { return null; } @@ -1286,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( @@ -1305,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); @@ -1326,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) + private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, List 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; @@ -1358,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)) @@ -1369,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); @@ -1394,7 +1440,7 @@ private static bool IsCmdletizationFile(RemoteDiscoveryHelper.CimModuleFile cimM return cimModuleFile.FileCode == RemoteDiscoveryHelper.CimFileCode.CmdletizationV1; } - private IEnumerable CreateCimModuleFiles( + private static IEnumerable CreateCimModuleFiles( RemoteDiscoveryHelper.CimModule remoteCimModule, RemoteDiscoveryHelper.CimFileCode fileCode, Func filesFilter, @@ -1734,28 +1780,16 @@ protected override void StopProcessing() #region IDisposable Members /// - /// Releases resources associated with this object. + /// Release all resources. /// public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases resources associated with this object. - /// - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } + _cancellationTokenSource.Dispose(); _disposed = true; } @@ -1820,14 +1854,14 @@ 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: delegate () - { - ImportModule_ViaLocalModuleInfo(importModuleOptions, module); - SetModuleBaseForEngineModules(module.Name, this.Context); - }, + localAction: () => + { + ImportModule_ViaLocalModuleInfo(importModuleOptions, module); + SetModuleBaseForEngineModules(module.Name, this.Context); + }, cimSessionAction: (cimSession, resourceUri, cimNamespace) => ImportModule_RemotelyViaCimSession( importModuleOptions, @@ -1846,13 +1880,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)) @@ -1882,7 +1914,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) @@ -1924,27 +1956,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); } } @@ -1956,38 +1987,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) + if (shouldLoadModuleLocally) { - ImportModule_LocallyViaName_WithTelemetry(importModuleOptions, coreModuleToLoad); - } - else - { - 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; @@ -1999,32 +2065,19 @@ 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) { - foreach(string moduleName in filteredModuleNames) + foreach (string moduleName in filteredModuleNames) { PrepareNoClobberWinCompatModuleImport(moduleName, null, ref importModuleOptions); } @@ -2032,19 +2085,32 @@ 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); @@ -2069,7 +2135,7 @@ internal override IList ImportModulesUsingWinCompat(IEnumerable 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; } /// /// This parameter specified a prefix used to modify names of imported commands. /// - internal string BasePrefix { set; get; } = string.Empty; + internal string BasePrefix { get; set; } = string.Empty; /// /// Flags -force operations. @@ -274,12 +281,27 @@ internal List MatchAll "DefaultCommandPrefix" }; - private static string[] s_moduleVersionMembers = new string[] { + private static readonly string[] s_moduleVersionMembers = new string[] { "ModuleName", "GUID", "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. @@ -305,16 +327,27 @@ internal List MatchAll /// internal static readonly object s_WindowsPowerShellCompatSyncObject = new object(); - private Dictionary _currentlyProcessingModules = new Dictionary(); + 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,16 +358,23 @@ 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)) { string moduleName = Path.GetFileName(folder); - if (string.Compare(moduleName, fileBaseName, StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(moduleName, fileBaseName, StringComparison.OrdinalIgnoreCase)) { fileBaseName = moduleName; #endif @@ -342,7 +382,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); } @@ -452,7 +492,7 @@ private Hashtable LoadModuleManifestData( } catch (RuntimeException pe) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { string message = StringUtil.Format(Modules.InvalidModuleManifest, scriptInfo.Path, pe.Message); MissingMemberException mm = new MissingMemberException(message); @@ -483,8 +523,8 @@ internal Hashtable LoadModuleManifestData( { string message; - var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); - var writingErrors = 0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors); + var importingModule = (manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0; + var writingErrors = (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0; // Load the data file(s) to get the module info... try @@ -556,7 +596,7 @@ internal Hashtable LoadModuleManifestData( if (validMembers != null && !ValidateManifestHash(data, validMembers, moduleManifestPath, manifestProcessingFlags)) { containedErrors = true; - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.NullOnFirstError)) + if ((manifestProcessingFlags & ManifestProcessingFlags.NullOnFirstError) != 0) return null; } @@ -591,9 +631,9 @@ private bool ValidateManifestHash( { if (badKeys.Length > 0) badKeys.Append(", "); - badKeys.Append("'"); + badKeys.Append('\''); badKeys.Append(s); - badKeys.Append("'"); + badKeys.Append('\''); } } @@ -602,7 +642,7 @@ private bool ValidateManifestHash( result = false; string message = null; - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { // Check for PowerShell Version before checking other keys // If a PowerShellVersion exists and does not match the requirements, then the error is InsufficientPowerShellVersion @@ -630,7 +670,7 @@ private bool ValidateManifestHash( validMembersString.Append(validMembers[i]); } - validMembersString.Append("'"); + validMembersString.Append('\''); message = StringUtil.Format(Modules.InvalidModuleManifestMember, moduleManifestPath, validMembersString, badKeys); InvalidOperationException ioe = new InvalidOperationException(message); ErrorRecord er = new ErrorRecord(ioe, "Modules_InvalidManifestMember", @@ -643,9 +683,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 loadTypesFiles, - bool loadFormatFiles, 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 +709,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 { @@ -723,7 +778,7 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module // NestedModules = 'test2' ---> test2 is a directory under current module directory (e.g - Test1) // We also need to look for Test1\Test2\Test2.(psd1/psm1/dll) // With the call above, we are only looking at Test1\Test2.(psd1/psm1/dll) - if (found == false && moduleFileFound == false) + if (!found && !moduleFileFound) { string newRootedPath = Path.Combine(rootedPath, moduleSpecification.Name); string newModuleBase = Path.Combine(moduleBase, moduleSpecification.Name); @@ -764,7 +819,7 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module // Win8: 262157 - Import-Module is giving errors while loading Nested Modules. (This is a V2 bug) // Only look for the file if the file was not found with the previous search - if (found == false && moduleFileFound == false) + if (!found && !moduleFileFound) { string newRootedPath = Path.Combine(rootedPath, moduleSpecification.Name); string newModuleBase = Path.Combine(moduleBase, moduleSpecification.Name); @@ -786,10 +841,10 @@ private PSModuleInfo LoadModuleNamedInManifest(PSModuleInfo parentModule, Module } // The rooted files wasn't found, so don't search anymore... - if (found == false && wasRooted) + if (!found && wasRooted) return null; - if (searchModulePath && found == false && moduleFileFound == false) + if (searchModulePath && !found && !moduleFileFound) { if (VerifyIfNestedModuleIsAvailable(moduleSpecification, null, null, out tempModuleInfoFromVerification)) { @@ -823,7 +878,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,17 +891,27 @@ 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); } } // At this point, we haven't found an actual module, so try loading it as a // PSSnapIn and then finally as an assembly in the GAC... - if ((found == false) && (moduleSpecification.Guid == null) && (moduleSpecification.Version == null) && (moduleSpecification.RequiredVersion == null) && (moduleSpecification.MaximumVersion == null)) + if (!found && (moduleSpecification.Guid == null) && (moduleSpecification.Version == null) && (moduleSpecification.RequiredVersion == null) && (moduleSpecification.MaximumVersion == null)) { // If we are in module analysis and the parent module declares non-wildcarded ExportedCmdlets, then we don't need to // actually process the binary module. @@ -878,19 +943,17 @@ 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, - true, // trySnapInName moduleSpecification.Name, - null, // fileName - null, // assemblyToLoad + fileName: null, + assemblyToLoad: null, moduleBase, ss, options, manifestProcessingFlags, prefix, - loadTypesFiles, - loadFormatFiles, out found, - shortModuleName, false); + shortModuleName, + disableFormatUpdates: false); } catch (FileNotFoundException) { @@ -962,7 +1025,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)) @@ -985,9 +1048,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; } } @@ -1016,9 +1078,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; } } @@ -1043,7 +1104,7 @@ private IEnumerable GetModuleForRootedPaths(List modulePat } } - private ErrorRecord CreateModuleNotFoundError(string modulePath) + private static ErrorRecord CreateModuleNotFoundError(string modulePath) { string errorMessage = StringUtil.Format(Modules.ModuleNotFoundForGetModule, modulePath); FileNotFoundException fnf = new FileNotFoundException(errorMessage); @@ -1055,35 +1116,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; } } @@ -1140,12 +1193,12 @@ internal static Version GetMaximumVersion(string stringVersion) else { // If first conversion fails, try to convert * to maximum version - string maxRange = "999999999"; + const string maxRange = "999999999"; if (stringVersion[stringVersion.Length - 1] == '*') { stringVersion = stringVersion.Substring(0, stringVersion.Length - 1); - stringVersion = stringVersion + maxRange; - int starNum = stringVersion.Count(x => x == '.'); + stringVersion += maxRange; + int starNum = stringVersion.Count(static x => x == '.'); for (int i = 0; i < (3 - starNum); i++) { stringVersion = stringVersion + '.' + maxRange; @@ -1421,7 +1474,7 @@ private IEnumerable CreateFakeModuleObject(IEnumerable(); - string moduleName = ModuleIntrinsics.GetModuleName(moduleManifestPath); expFeatureList = new List(features.Length); foreach (Hashtable feature in features) @@ -2129,70 +2193,86 @@ 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 = FixupFileName(moduleBase, assembly, StringLiterals.PowerShellILAssemblyExtension, importingModule); + fileName = FixFileNameWithoutLoadingAssembly(moduleBase, assembly, extToTry, out isPathResolved); + if (isPathResolved) + { + break; + } } - string loadMessage = StringUtil.Format(Modules.LoadingFile, "Assembly", fileName); - WriteVerbose(loadMessage); - iss.Assemblies.Add(new SessionStateAssemblyEntry(assembly, fileName)); - fixedUpAssemblyPathList.Add(fileName); + if (!isPathResolved) + { + // 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); + } + } - 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); + WriteVerbose(StringUtil.Format(Modules.LoadingFile, "Assembly", fileName)); - doBind = true; - } + // 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)); + + iss.Assemblies.Add(assemblyEntry); + doBind = true; } } } // Set up to load any types files that have been specified... - List exportedTypeFiles; - if ( - !GetListOfFilesFromData(data, moduleManifestPath, "TypesToProcess", manifestProcessingFlags, moduleBase, - ".ps1xml", true, out exportedTypeFiles)) + if (!GetListOfFilesFromData( + data, + moduleManifestPath, + key: "TypesToProcess", + manifestProcessingFlags, + moduleBase, + extension: ".ps1xml", + verifyFilesExist: true, + out List exportedTypeFiles)) { containedErrors = true; if (bailOnFirstError) return null; @@ -2215,8 +2295,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; @@ -2234,10 +2313,15 @@ internal PSModuleInfo LoadModuleManifest( } // Set up to load any format files that have been specified... - List exportedFormatFiles; - if ( - !GetListOfFilesFromData(data, moduleManifestPath, "FormatsToProcess", manifestProcessingFlags, - moduleBase, ".ps1xml", true, out exportedFormatFiles)) + if (!GetListOfFilesFromData( + data, + moduleManifestPath, + key: "FormatsToProcess", + manifestProcessingFlags, + moduleBase, + extension: ".ps1xml", + verifyFilesExist: true, + out List exportedFormatFiles)) { containedErrors = true; if (bailOnFirstError) return null; @@ -2276,10 +2360,15 @@ internal PSModuleInfo LoadModuleManifest( } // scripts to process - List scriptsToProcess; - if ( - !GetListOfFilesFromData(data, moduleManifestPath, "ScriptsToProcess", manifestProcessingFlags, - moduleBase, ".ps1", true, out scriptsToProcess)) + if (!GetListOfFilesFromData( + data, + moduleManifestPath, + key: "ScriptsToProcess", + manifestProcessingFlags, + moduleBase, + extension: ".ps1", + verifyFilesExist: true, + out List scriptsToProcess)) { containedErrors = true; if (bailOnFirstError) return null; @@ -2323,13 +2412,16 @@ internal PSModuleInfo LoadModuleManifest( } // Process "FileList" - List fileList; - if ( - !GetListOfFilesFromData(data, moduleManifestPath, "FileList", manifestProcessingFlags, moduleBase, "" - /*extension*/, - false - /* don't check file existence - don't want to change current behavior without feature team discussion */, - out fileList)) + if (!GetListOfFilesFromData( + data, + moduleManifestPath, + key: "FileList", + manifestProcessingFlags, + moduleBase, + extension: null, + // Don't check file existence - don't want to change current behavior without feature team discussion. + verifyFilesExist: false, + out List fileList)) { containedErrors = true; if (bailOnFirstError) return null; @@ -2365,17 +2457,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 @@ -2470,38 +2558,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; @@ -2927,8 +3004,6 @@ internal PSModuleInfo LoadModuleManifest( ss: null, options: nestedModuleOptions, manifestProcessingFlags: manifestProcessingFlags, - loadTypesFiles: true, - loadFormatFiles: true, privateData: privateData, found: out found, shortModuleName: null, @@ -3030,8 +3105,6 @@ internal PSModuleInfo LoadModuleManifest( ss: ss, options: options, manifestProcessingFlags: manifestProcessingFlags, - loadTypesFiles: (exportedTypeFiles == null || 0 == exportedTypeFiles.Count), // If types files already loaded, don't load snapin files - loadFormatFiles: (exportedFormatFiles == null || 0 == exportedFormatFiles.Count), // if format files already loaded, don't load snapin files privateData: privateData, found: out found, shortModuleName: null, @@ -3136,16 +3209,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) @@ -3213,7 +3280,7 @@ internal PSModuleInfo LoadModuleManifest( newManifestInfo.Prefix = resolvedCommandPrefix; } - if (newManifestInfo.FileList == null || newManifestInfo.FileList.LongCount() == 0) + if (newManifestInfo.FileList == null || !newManifestInfo.FileList.Any()) { if (fileList != null) { @@ -3224,7 +3291,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.ModuleList == null || newManifestInfo.ModuleList.LongCount() == 0) + if (newManifestInfo.ModuleList == null || !newManifestInfo.ModuleList.Any()) { if (moduleList != null) { @@ -3235,7 +3302,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.CompatiblePSEditions == null || newManifestInfo.CompatiblePSEditions.LongCount() == 0) + if (newManifestInfo.CompatiblePSEditions == null || !newManifestInfo.CompatiblePSEditions.Any()) { if (compatiblePSEditions != null) { @@ -3248,7 +3315,7 @@ internal PSModuleInfo LoadModuleManifest( newManifestInfo.ProcessorArchitecture = requiredProcessorArchitecture; } - if (newManifestInfo.RequiredAssemblies == null || newManifestInfo.RequiredAssemblies.LongCount() == 0) + if (newManifestInfo.RequiredAssemblies == null || !newManifestInfo.RequiredAssemblies.Any()) { if (assemblyList != null) { @@ -3259,7 +3326,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.Scripts == null || newManifestInfo.Scripts.LongCount() == 0) + if (newManifestInfo.Scripts == null || !newManifestInfo.Scripts.Any()) { if (scriptsToProcess != null) { @@ -3270,10 +3337,7 @@ internal PSModuleInfo LoadModuleManifest( } } - if (newManifestInfo.RootModuleForManifest == null) - { - newManifestInfo.RootModuleForManifest = manifestInfo.RootModuleForManifest; - } + newManifestInfo.RootModuleForManifest ??= manifestInfo.RootModuleForManifest; if (newManifestInfo.DeclaredCmdletExports == null || newManifestInfo.DeclaredCmdletExports.Count == 0) { @@ -3365,13 +3429,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, @@ -3531,7 +3607,7 @@ private static void PropagateExportedTypesFromNestedModulesToRootModuleScope(Imp if (nestedModule != null) { var exportedTypes = nestedModule.GetExportedTypeDefinitions(); - if (exportedTypes != null && exportedTypes.Count != 0) + if (exportedTypes != null && exportedTypes.Count > 0) { foreach (var t in exportedTypes) { @@ -3646,7 +3722,7 @@ private static void WriteInvalidManifestMemberError( Exception e, ManifestProcessingFlags manifestProcessingFlags) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { ErrorRecord er = GenerateInvalidModuleMemberErrorRecord(manifestElement, moduleManifestPath, e); cmdlet.WriteError(er); @@ -3696,7 +3772,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; @@ -3750,7 +3826,7 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, ManifestProcessingFlags manifestProcessingFlags, out ErrorRecord error) { - Dbg.Assert(0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements), "LoadRequiredModule / RequiredModules checks should only be done when actually loading a module"); + Dbg.Assert((manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0, "LoadRequiredModule / RequiredModules checks should only be done when actually loading a module"); error = null; @@ -3817,7 +3893,7 @@ internal static PSModuleInfo LoadRequiredModule(ExecutionContext context, string message; if (moduleManifestPath != null) { - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { switch (loadFailureReason) { @@ -4203,7 +4279,7 @@ private static bool HasRequiredModulesCyclicReference(ModuleSpecification curren /// Search for a localized psd1 manifest file, using the same algorithm /// as Import-LocalizedData. /// - private ExternalScriptInfo FindLocalizedModuleManifest(string path) + private static ExternalScriptInfo FindLocalizedModuleManifest(string path) { string dir = Path.GetDirectoryName(path); string file = Path.GetFileName(path); @@ -4214,9 +4290,9 @@ private ExternalScriptInfo FindLocalizedModuleManifest(string path) while (currentCulture != null && !string.IsNullOrEmpty(currentCulture.Name)) { StringBuilder stringBuilder = new StringBuilder(dir); - stringBuilder.Append("\\"); + stringBuilder.Append('\\'); stringBuilder.Append(currentCulture.Name); - stringBuilder.Append("\\"); + stringBuilder.Append('\\'); stringBuilder.Append(file); string filePath = stringBuilder.ToString(); @@ -4242,7 +4318,7 @@ private ExternalScriptInfo FindLocalizedModuleManifest(string path) /// /// Checks to see if the module manifest contains the specified key. /// If it does and it's valid, it returns true otherwise it returns false. - /// If the key wasn't there or wasn't valid, then is set to null + /// If the key wasn't there or wasn't valid, then is set to /// /// The hashtable to look for the key in. /// The manifest that generated the hashtable. @@ -4281,7 +4357,7 @@ internal bool GetListOfStringsFromData( /// /// Checks to see if the module manifest contains the specified key. /// If it does and it's valid, it returns true otherwise it returns false. - /// If the key wasn't there or wasn't valid, then is set to null. + /// If the key wasn't there or wasn't valid, then is set to . /// /// The hashtable to look for the key in. /// The manifest that generated the hashtable. @@ -4334,7 +4410,7 @@ private bool GetListOfWildcardsFromData( /// /// Checks to see if the module manifest contains the specified key. /// If it does and it's valid, it returns true otherwise it returns false. - /// If the key wasn't there or wasn't valid, then is set to null + /// If the key wasn't there or wasn't valid, then is set to /// /// The hashtable to look for the key in. /// The manifest that generated the hashtable. @@ -4342,7 +4418,7 @@ private bool GetListOfWildcardsFromData( /// Specifies how to treat errors and whether to load elements. /// Base directory of a module. /// Expected file extension (added to strings that didn't have an extension). - /// If true then we want to error out if the specified files don't exist. + /// If then we want to error out if the specified files don't exist. /// Returns the extracted version. /// private bool GetListOfFilesFromData( @@ -4356,8 +4432,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; @@ -4379,7 +4453,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) || @@ -4435,7 +4509,7 @@ internal void SetModuleLoggingInformation(PSModuleInfo m) } } - private void SetModuleLoggingInformation(ModuleLoggingGroupPolicyStatus status, PSModuleInfo m, IEnumerable moduleNames) + private static void SetModuleLoggingInformation(ModuleLoggingGroupPolicyStatus status, PSModuleInfo m, IEnumerable moduleNames) { // TODO, insivara : What happens when Enabled but none of the other options (DefaultSystemModules, NonDefaultSystemModule, NonSystemModule, SpecificModules) are set? // After input from GP team for this behavior, need to revisit the commented out part @@ -4489,16 +4563,16 @@ 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 true and sets to the value. - /// If the key is missing it returns true and sets to default(). - /// If the key is invalid then it returns false. + /// 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 invalid then it returns . /// /// The hashtable to look for the key in. /// The manifest that generated the hashtable. /// The table key to use. /// Specifies how to treat errors and whether to load elements. /// Value from the manifest converted to the right type. - /// true if success; false if there were errors. + /// if success; if there were errors. internal bool GetScalarFromData( Hashtable data, string moduleManifestPath, @@ -4521,7 +4595,7 @@ internal bool GetScalarFromData( catch (PSInvalidCastException e) { result = default(T); - if (0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors)) + if ((manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0) { string message = StringUtil.Format(Modules.ModuleManifestInvalidValue, key, e.Message, moduleManifestPath); ArgumentException newAe = new ArgumentException(message); @@ -4534,39 +4608,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. @@ -4578,24 +4671,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; @@ -4608,12 +4701,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; @@ -4868,12 +4961,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(); } @@ -4927,8 +5020,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) @@ -4936,7 +5027,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) { @@ -4944,19 +5035,16 @@ 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 == false) + if (module.ImplementingAssembly != null && !module.ImplementingAssembly.IsDynamic) { var exportedTypes = PSSnapInHelpers.GetAssemblyTypes(module.ImplementingAssembly, module.Name); foreach (var type in exportedTypes) @@ -5168,19 +5256,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); } } } @@ -5230,7 +5312,7 @@ internal bool DoesAlreadyLoadedModuleSatisfyConstraints(PSModuleInfo alreadyLoad /// /// /// Returns PSModuleInfo of an already loaded module if that module can be simply reimported and there is no need to proceed with a regular import. - /// Returns null if the caller should proceed with a regular import (either because there is no previously loaded module, or because the -Force flag was specified and the previously loaded module has been removed by this method). + /// Returns if the caller should proceed with a regular import (either because there is no previously loaded module, or because the -Force flag was specified and the previously loaded module has been removed by this method). /// internal PSModuleInfo IsModuleImportUnnecessaryBecauseModuleIsAlreadyLoaded(string modulePath, string prefix, ImportModuleOptions options) { @@ -5314,9 +5396,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 _); } /// @@ -5349,7 +5430,7 @@ internal PSModuleInfo LoadUsingExtensions(PSModuleInfo parentModule, else extensions = ModuleIntrinsics.PSModuleExtensions; - var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); + var importingModule = (manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0; // "ni.dll" has a higher priority then ".dll" to be loaded. for (int i = 0; i < extensions.Length; i++) @@ -5417,7 +5498,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)) { @@ -5556,8 +5636,8 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str return null; } - var importingModule = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); - var writingErrors = 0 != (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors); + var importingModule = (manifestProcessingFlags & ManifestProcessingFlags.LoadElements) != 0; + var writingErrors = (manifestProcessingFlags & ManifestProcessingFlags.WriteErrors) != 0; // In case the file is a Ngen Assembly. string ext; @@ -5573,12 +5653,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... @@ -5662,19 +5753,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, @@ -5684,8 +5787,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) { @@ -5693,10 +5796,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); @@ -5850,7 +5953,7 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str if (module != null) { - CheckForDisallowedDotSourcing(module.SessionState, psd1ScriptInfo, options); + CheckForDisallowedDotSourcing(module, psd1ScriptInfo, options); if (importingModule) { @@ -5872,8 +5975,18 @@ internal PSModuleInfo LoadModule(PSModuleInfo parentModule, string fileName, str ext.Equals(StringLiterals.PowerShellNgenAssemblyExtension, StringComparison.OrdinalIgnoreCase) || ext.Equals(StringLiterals.PowerShellILExecutableExtension, StringComparison.OrdinalIgnoreCase)) { - module = LoadBinaryModule(false, ModuleIntrinsics.GetModuleName(fileName), fileName, null, - moduleBase, ss, options, manifestProcessingFlags, prefix, true, true, out found); + module = LoadBinaryModule( + parentModule, + ModuleIntrinsics.GetModuleName(fileName), + fileName, + assemblyToLoad: null, + moduleBase, + ss, + options, + manifestProcessingFlags, + prefix, + out found); + if (found && module != null) { // LanguageMode does not apply to binary modules @@ -6005,25 +6118,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 => { @@ -6034,15 +6150,50 @@ 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) { bool shouldProcessModule = true; @@ -6071,9 +6222,9 @@ private static bool ShouldProcessScriptModule(PSModuleInfo parentModule, ref boo return shouldProcessModule; } - private static object s_lockObject = new object(); + private static readonly object s_lockObject = new object(); - private void ClearAnalysisCaches() + private static void ClearAnalysisCaches() { lock (s_lockObject) { @@ -6083,10 +6234,9 @@ private void ClearAnalysisCaches() } // Analyzes a binary module implementation for its cmdlets. - private static Dictionary> s_binaryAnalysisCache = + 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. /// @@ -6126,158 +6276,9 @@ 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 Dictionary s_scriptAnalysisCache = new Dictionary(); + private static readonly Dictionary s_scriptAnalysisCache = new Dictionary(); private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionContext context) { @@ -6385,7 +6386,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon try { - foreach (string item in System.IO.Directory.GetFiles(baseDirectory, "*.ps1")) + foreach (string item in System.IO.Directory.EnumerateFiles(baseDirectory, "*.ps1")) { module.AddDetectedFunctionExport(Path.GetFileNameWithoutExtension(item)); } @@ -6404,7 +6405,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon // If this has an extension, and it's a relative path, // then we need to ensure it's a fully-qualified path - if ((moduleToProcess.IndexOfAny(Path.GetInvalidPathChars()) == -1) && + if ((!PathUtils.ContainsInvalidPathChars(moduleToProcess)) && Path.HasExtension(moduleToProcess) && (!Path.IsPathRooted(moduleToProcess))) { @@ -6493,7 +6494,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 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. @@ -6505,25 +6506,40 @@ 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, string moduleName, string fileName, - Assembly assemblyToLoad, string moduleBase, SessionState ss, ImportModuleOptions options, - ManifestProcessingFlags manifestProcessingFlags, string prefix, - bool loadTypes, bool loadFormats, out bool found) + internal PSModuleInfo LoadBinaryModule( + PSModuleInfo parentModule, + string moduleName, + string fileName, + Assembly assemblyToLoad, + string moduleBase, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + string prefix, + out bool found) { - return LoadBinaryModule(null, trySnapInName, moduleName, fileName, assemblyToLoad, moduleBase, ss, options, - manifestProcessingFlags, prefix, loadTypes, loadFormats, out found, null, false); + return LoadBinaryModule( + parentModule, + moduleName, + fileName, + assemblyToLoad, + moduleBase, + ss, + options, + manifestProcessingFlags, + prefix, + out found, + shortModuleName: null, + disableFormatUpdates: false); } /// /// 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 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. @@ -6536,47 +6552,50 @@ internal PSModuleInfo LoadBinaryModule(bool trySnapInName, string moduleName, st /// 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, string moduleBase, SessionState ss, ImportModuleOptions options, ManifestProcessingFlags manifestProcessingFlags, string prefix, bool loadTypes, bool loadFormats, out bool found, string shortModuleName, bool disableFormatUpdates) + internal PSModuleInfo LoadBinaryModule( + PSModuleInfo parentModule, + string moduleName, + string fileName, + Assembly assemblyToLoad, + string moduleBase, + SessionState ss, + ImportModuleOptions options, + ManifestProcessingFlags manifestProcessingFlags, + string prefix, + 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 = 0 != (manifestProcessingFlags & ManifestProcessingFlags.LoadElements); + 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) @@ -6584,191 +6603,73 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn // 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 (importingModule) - { - snapin = iss.ImportPSSnapIn(moduleName, out warning); - } - } - catch (PSArgumentException) + if (error != null) { - // BUGBUG - brucepay - probably want to have a verbose message here... + throw error; } -#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 == false) - { - if (importingModule) - { - assembly = Context.AddAssembly(moduleName, fileName, out error); - - if (assembly == null) - { - if (error != null) - throw error; - - found = false; - return null; - } - - assemblyVersion = GetAssemblyVersionNumber(assembly); - - if (string.IsNullOrEmpty(fileName)) - modulePath = assembly.Location; - else - modulePath = fileName; + assemblyVersion = GetAssemblyVersionNumber(assembly); + modulePath = string.IsNullOrEmpty(fileName) ? assembly.Location : 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... @@ -6882,16 +6783,7 @@ internal PSModuleInfo LoadBinaryModule(PSModuleInfo parentModule, bool trySnapIn 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 = new Type[] { }; - 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... @@ -7081,17 +6973,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 == false && - privateDataHashTable != null && privateDataHashTable.ContainsKey("ImplicitRemoting")) - { - context.Modules.IsImplicitRemotingModuleLoaded = true; - } + targetSessionState.Module?.AddNestedModule(module); } /// @@ -7393,6 +7275,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; @@ -7542,29 +7427,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. @@ -7577,7 +7439,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 @@ -7585,7 +7447,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 b20e11e4745..538c4775f0a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -10,9 +10,7 @@ using System.Management.Automation.Language; using System.Text; using System.Threading; - using Microsoft.PowerShell.Commands; -using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; @@ -39,30 +37,25 @@ public class ModuleIntrinsics private static readonly string s_windowsPowerShellPSHomeModulePath = Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules"); + static ModuleIntrinsics() + { + // Initialize the module path. + SetModulePath(); + } + internal ModuleIntrinsics(ExecutionContext context) { _context = context; - - // And initialize the module path... - SetModulePath(); + ModuleTable = new Dictionary(StringComparer.OrdinalIgnoreCase); } private readonly ExecutionContext _context; // Holds the module collection... - internal Dictionary ModuleTable { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); + internal Dictionary ModuleTable { get; } 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 +143,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; @@ -188,11 +178,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object sb.SessionState = ss; } - else + else if (moduleCode is string sbText) { - var sbText = moduleCode as string; - if (sbText != null) - sb = ScriptBlock.Create(_context, sbText); + sb = ScriptBlock.Create(_context, sbText); } } @@ -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(); } /// @@ -703,9 +688,9 @@ internal static bool MatchesModulePath(string modulePath, string requiredPath) } #if UNIX - StringComparison strcmp = StringComparison.Ordinal; + const StringComparison strcmp = StringComparison.Ordinal; #else - StringComparison strcmp = StringComparison.OrdinalIgnoreCase; + const StringComparison strcmp = StringComparison.OrdinalIgnoreCase; #endif // We must check modulePath (e.g. /path/to/module/module.psd1) against several possibilities: @@ -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,10 @@ 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, Environment.SpecialFolderOption.DoNotVerify); + return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory); #endif } @@ -985,20 +988,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) { @@ -1014,7 +1014,7 @@ internal static string GetPSHomeModulePath() /// It's known as "Program Files" module path in windows powershell. /// /// - private static string GetSharedModulePath() + internal static string GetSharedModulePath() { #if UNIX return Platform.SelectProductNameForDirectory(Platform.XDG_Type.SHARED_MODULES); @@ -1085,86 +1085,118 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa } /// - /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%) + /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. /// - /// 'Combined path' string to analyze; can not be null. - /// Path to search for; can not be another 'combined path' (semicolon-separated); can not be null. - /// Index of pathToLookFor in pathToScan; -1 if not found. - private static int PathContainsSubstring(string pathToScan, string pathToLookFor) + /// Path string (like %Path% or %PSModulePath%). + /// An individual path to add, or multiple paths separated by the path separator character. + /// -1 to append to the end; 0 to insert in the beginning of the string; etc... + /// Result string. + private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition) { // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function"); - Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); + Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function"); + Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + + // The 'pathToAdd' could be a 'combined path' (path-separator-separated). + string[] newPaths = pathToAdd.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - 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 goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - foreach (string substring in substrings) + if (newPaths.Length is 0) { - string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison + // The 'pathToAdd' doesn't really contain any paths to add. + return basePath; + } + + var result = new StringBuilder(basePath, capacity: basePath.Length + pathToAdd.Length + newPaths.Length); + var addedPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + string[] initialPaths = basePath.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (string p in initialPaths) + { + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + addedPaths.Add(Path.TrimEndingDirectorySeparator(p)); + } - // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') - // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } + foreach (string subPathToAdd in newPaths) + { + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + string normalizedPath = Path.TrimEndingDirectorySeparator(subPathToAdd); + if (addedPaths.Contains(normalizedPath)) + { + // The normalized sub path was already added - skip it. + continue; + } - if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase)) + // The normalized sub path was not found - add it. + if (insertPosition is -1 || insertPosition >= result.Length) { - return pos; // match found - return index of it in the 'pathToScan' string + // Append the normalized sub path to the end. + if (result.Length > 0 && result[^1] != Path.PathSeparator) + { + result.Append(Path.PathSeparator); + } + + result.Append(normalizedPath); + // Next insertion should happen at the end. + insertPosition = result.Length; } else { - pos += substring.Length + 1; // '1' is for trailing semicolon + // Insert at the requested location. + // This is used by the user-specific module path, the shared module path ( location), and the PSHome module path. + string strToInsert = normalizedPath + Path.PathSeparator; + result.Insert(insertPosition, strToInsert); + + // Next insertion should happen after the just inserted string. + insertPosition += strToInsert.Length; } + + // Add it to the set. + addedPaths.Add(normalizedPath); } - // if we are here, that means a match was not found - return -1; + + return result.ToString(); } /// - /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. + /// The available module path scopes. /// - /// Path string (like %Path% or %PSModulePath%). - /// Collection of individual paths to add. - /// -1 to append to the end; 0 to insert in the beginning of the string; etc... - /// Result string. - private static string AddToPath(string basePath, string pathToAdd, int insertPosition) + public enum PSModulePathScope { - // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); - Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + /// The users module path. + User, - StringBuilder result = new StringBuilder(basePath); + /// The Builtin module path. This is where PowerShell is installed (PSHOME). + Builtin, - 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) - { - 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 - { - bool endsWithPathSeparator = false; - if (result.Length > 0) endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); + /// The machine module path. This is the shared location for all users of the system. + Machine + } - 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 - 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)) - { - result.Insert(insertPosition, subPathToAdd + Path.PathSeparator); - } - } - } + /// + /// 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(); } - - return result.ToString(); } /// @@ -1177,7 +1209,7 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location // If the variable isn't set, then set it to the default value - if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case + if (string.IsNullOrEmpty(currentProcessModulePath)) // EVT.Process does Not exist - really corner case { // Handle the default case... if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to location @@ -1189,7 +1221,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,11 +1250,12 @@ 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; @@ -1233,7 +1274,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 +1299,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); } } } @@ -1294,11 +1334,16 @@ private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); #if !UNIX - // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete - // otherwise, the user modified it and we should use the process one + // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete. + // Otherwise, the user modified it and we should use the process one. if (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0) { - currentModulePath = currentModulePath + Path.PathSeparator + GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + string machineScopeValue = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + currentModulePath = string.IsNullOrEmpty(currentModulePath) + ? machineScopeValue + : string.IsNullOrEmpty(machineScopeValue) + ? currentModulePath + : string.Concat(currentModulePath, Path.PathSeparator, machineScopeValue); } #endif string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers); @@ -1336,7 +1381,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,35 +1467,13 @@ 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( - delegate (T x, T y) + (T x, T y) => { string kx = keyGetter(x); string ky = keyGetter(y); @@ -1458,24 +1481,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 +1545,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedFunctions, delegate (FunctionInfo ci) { return ci.Name; }); + SortAndRemoveDuplicates(sessionState.ExportedFunctions, static (FunctionInfo ci) => ci.Name); } if (cmdletPatterns != null) @@ -1582,7 +1600,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.Module.CompiledExports, delegate (CmdletInfo ci) { return ci.Name; }); + SortAndRemoveDuplicates(sessionState.Module.CompiledExports, static (CmdletInfo ci) => ci.Name); } if (variablePatterns != null) @@ -1605,7 +1623,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedVariables, delegate (PSVariable v) { return v.Name; }); + SortAndRemoveDuplicates(sessionState.ExportedVariables, static (PSVariable v) => v.Name); } if (aliasPatterns != null) @@ -1645,7 +1663,7 @@ internal static void ExportModuleMembers( } } - SortAndRemoveDuplicates(sessionState.ExportedAliases, delegate (AliasInfo ci) { return ci.Name; }); + SortAndRemoveDuplicates(sessionState.ExportedAliases, static (AliasInfo ci) => ci.Name); } } @@ -1711,14 +1729,16 @@ 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. /// +#nullable enable public interface IModuleAssemblyInitializer { /// diff --git a/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs b/src/System.Management.Automation/engine/Modules/ModuleSpecification.cs index 5333e364502..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) @@ -123,14 +118,14 @@ internal static Exception ModuleSpecificationInitHelper(ModuleSpecification modu badKeys.Append(", "); } - badKeys.Append("'"); + badKeys.Append('\''); badKeys.Append(entry.Key.ToString()); - badKeys.Append("'"); + badKeys.Append('\''); } } } // 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; @@ -197,7 +232,7 @@ public override string ToString() var moduleSpecBuilder = new StringBuilder(); - moduleSpecBuilder.Append("@{ ModuleName = '").Append(Name).Append("'"); + moduleSpecBuilder.Append("@{ ModuleName = '").Append(Name).Append('\''); if (Guid != null) { @@ -206,18 +241,18 @@ public override string ToString() if (RequiredVersion != null) { - moduleSpecBuilder.Append("; RequiredVersion = '").Append(RequiredVersion).Append("'"); + moduleSpecBuilder.Append("; RequiredVersion = '").Append(RequiredVersion).Append('\''); } else { if (Version != null) { - moduleSpecBuilder.Append("; ModuleVersion = '").Append(Version).Append("'"); + moduleSpecBuilder.Append("; ModuleVersion = '").Append(Version).Append('\''); } if (MaximumVersion != null) { - moduleSpecBuilder.Append("; MaximumVersion = '").Append(MaximumVersion).Append("'"); + moduleSpecBuilder.Append("; MaximumVersion = '").Append(MaximumVersion).Append('\''); } } diff --git a/src/System.Management.Automation/engine/Modules/ModuleUtils.cs b/src/System.Management.Automation/engine/Modules/ModuleUtils.cs index b825a54fbab..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; @@ -615,4 +659,3 @@ public CommandScore(CommandInfo command, int score) public int Score; } } - diff --git a/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs b/src/System.Management.Automation/engine/Modules/NewModuleCommand.cs index f61d3bb39bb..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. @@ -27,9 +28,9 @@ public sealed class NewModuleCommand : ModuleCmdletBase [Parameter(ParameterSetName = "Name", Mandatory = true, ValueFromPipeline = true, Position = 0)] public string Name { - set { _name = value; } - get { return _name; } + + set { _name = value; } } private string _name; @@ -42,7 +43,10 @@ public string Name [ValidateNotNull] public ScriptBlock ScriptBlock { - get { return _scriptBlock; } + get + { + return _scriptBlock; + } set { @@ -60,6 +64,11 @@ public ScriptBlock ScriptBlock [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Function { + get + { + return _functionImportList; + } + set { if (value == null) @@ -74,8 +83,6 @@ public string[] Function BaseFunctionPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _functionImportList; } } private string[] _functionImportList = Array.Empty(); @@ -88,6 +95,11 @@ public string[] Function [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Cmdlet { + get + { + return _cmdletImportList; + } + set { if (value == null) @@ -102,8 +114,6 @@ public string[] Cmdlet BaseCmdletPatterns.Add(WildcardPattern.Get(pattern, WildcardOptions.IgnoreCase)); } } - - get { return _cmdletImportList; } } private string[] _cmdletImportList = Array.Empty(); @@ -159,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 8ee68283ec6..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; } } @@ -534,7 +534,7 @@ public string DefaultCommandPrefix /// /// The string to quote. /// The quoted string. - private string QuoteName(string name) + private static string QuoteName(string name) { if (name == null) return "''"; @@ -546,7 +546,7 @@ private string QuoteName(string name) /// /// The Uri to quote. /// The quoted AbsoluteUri. - private string QuoteName(Uri name) + private static string QuoteName(Uri name) { if (name == null) return "''"; @@ -558,7 +558,7 @@ private string QuoteName(Uri name) /// /// The Version object to quote. /// The quoted Version string. - private string QuoteName(Version name) + private static string QuoteName(Version name) { if (name == null) return "''"; @@ -572,7 +572,7 @@ private string QuoteName(Version name) /// The list to quote. /// Streamwriter to get end of line character from. /// The quoted list. - private string QuoteNames(IEnumerable names, StreamWriter streamWriter) + private static string QuoteNames(IEnumerable names, StreamWriter streamWriter) { if (names == null) return "@()"; @@ -622,13 +622,13 @@ private string QuoteNames(IEnumerable names, StreamWriter streamWriter) /// /// /// - private IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) + private static IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) { if (moduleSpecs != null) { foreach (object spec in moduleSpecs) { - if (!(spec is Hashtable)) + if (spec is not Hashtable) { yield return spec.ToString(); } @@ -647,7 +647,7 @@ private IEnumerable PreProcessModuleSpec(IEnumerable moduleSpecs) /// The list to quote. /// Streamwriter to get end of line character from. /// The quoted list. - private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) + private static string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) { StringBuilder result = new StringBuilder(); result.Append("@("); @@ -662,8 +662,7 @@ private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) continue; } - ModuleSpecification moduleSpecification = (ModuleSpecification) - LanguagePrimitives.ConvertTo( + ModuleSpecification moduleSpecification = (ModuleSpecification)LanguagePrimitives.ConvertTo( spec, typeof(ModuleSpecification), CultureInfo.InvariantCulture); @@ -717,12 +716,12 @@ private string QuoteModules(IEnumerable moduleSpecs, StreamWriter streamWriter) result.Append("; "); } - result.Append("}"); + result.Append('}'); } } } - result.Append(")"); + result.Append(')'); return result.ToString(); } @@ -887,17 +886,15 @@ 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 string ManifestComment(string insert, StreamWriter streamWriter) + private static string ManifestComment(string insert, StreamWriter streamWriter) { // Prefix a non-empty string with a space for formatting reasons... if (!string.IsNullOrEmpty(insert)) @@ -949,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"); @@ -967,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); @@ -1031,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); @@ -1094,7 +1087,7 @@ protected override void EndProcessing() BuildModuleManifest(result, nameof(DefaultCommandPrefix), Modules.DefaultCommandPrefix, !string.IsNullOrEmpty(_defaultCommandPrefix), () => QuoteName(_defaultCommandPrefix), streamWriter); - result.Append("}"); + result.Append('}'); result.Append(streamWriter.NewLine); result.Append(streamWriter.NewLine); string strResult = result.ToString(); @@ -1188,7 +1181,7 @@ private void BuildPrivateDataInModuleManifest(StringBuilder result, StreamWriter BuildModuleManifest(result, nameof(IconUri), Modules.IconUri, IconUri != null, () => QuoteName(IconUri), streamWriter); BuildModuleManifest(result, nameof(ReleaseNotes), Modules.ReleaseNotes, !string.IsNullOrEmpty(ReleaseNotes), () => QuoteName(ReleaseNotes), streamWriter); BuildModuleManifest(result, nameof(Prerelease), Modules.Prerelease, !string.IsNullOrEmpty(Prerelease), () => QuoteName(Prerelease), streamWriter); - BuildModuleManifest(result, nameof(RequireLicenseAcceptance), Modules.RequireLicenseAcceptance, RequireLicenseAcceptance.IsPresent, () => { return RequireLicenseAcceptance.IsPresent ? "$true" : "$false"; }, streamWriter); + BuildModuleManifest(result, nameof(RequireLicenseAcceptance), Modules.RequireLicenseAcceptance, RequireLicenseAcceptance.IsPresent, () => RequireLicenseAcceptance.IsPresent ? "$true" : "$false", streamWriter); BuildModuleManifest(result, nameof(ExternalModuleDependencies), Modules.ExternalModuleDependencies, ExternalModuleDependencies != null && ExternalModuleDependencies.Length > 0, () => QuoteNames(ExternalModuleDependencies, streamWriter), streamWriter); result.Append(" } "); diff --git a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs index 033382a565b..4315f8bcb2b 100644 --- a/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs +++ b/src/System.Management.Automation/engine/Modules/PSModuleInfo.cs @@ -28,7 +28,7 @@ public sealed class PSModuleInfo new ReadOnlyDictionary(new Dictionary(StringComparer.OrdinalIgnoreCase)); // This dictionary doesn't include ExportedTypes from nested modules. - private ReadOnlyDictionary _exportedTypeDefinitionsNoNested { set; get; } + private ReadOnlyDictionary _exportedTypeDefinitionsNoNested { get; set; } private static readonly HashSet s_scriptModuleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -299,8 +299,7 @@ public string ModuleBase { get { - return _moduleBase ?? - (_moduleBase = !string.IsNullOrEmpty(Path) ? IO.Path.GetDirectoryName(Path) : string.Empty); + return _moduleBase ??= !string.IsNullOrEmpty(Path) ? IO.Path.GetDirectoryName(Path) : string.Empty; } } @@ -454,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; } @@ -471,7 +470,10 @@ public string Author /// public ModuleAccessMode AccessMode { - get { return _accessMode; } + get + { + return _accessMode; + } set { @@ -543,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) { @@ -582,7 +587,7 @@ public Dictionary ExportedFunctions } } - private bool IsScriptModuleFile(string path) + private static bool IsScriptModuleFile(string path) { var ext = System.IO.Path.GetExtension(path); return ext != null && s_scriptModuleExtensions.Contains(ext); @@ -659,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)); } /// @@ -705,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) { @@ -876,7 +884,7 @@ public IEnumerable CompatiblePSEditions get { return _compatiblePSEditions; } } - private List _compatiblePSEditions = new List(); + private readonly List _compatiblePSEditions = new List(); internal void AddToCompatiblePSEditions(string psEdition) { @@ -922,8 +930,7 @@ public ReadOnlyCollection NestedModules { get { - return _readonlyNestedModules ?? - (_readonlyNestedModules = new ReadOnlyCollection(_nestedModules)); + return _readonlyNestedModules ??= new ReadOnlyCollection(_nestedModules); } } @@ -1014,8 +1021,7 @@ public ReadOnlyCollection RequiredModules { get { - return _readonlyRequiredModules ?? - (_readonlyRequiredModules = new ReadOnlyCollection(_requiredModules)); + return _readonlyRequiredModules ??= new ReadOnlyCollection(_requiredModules); } } @@ -1040,8 +1046,7 @@ internal ReadOnlyCollection RequiredModulesSpecification { get { - return _readonlyRequiredModulesSpecification ?? - (_readonlyRequiredModulesSpecification = new ReadOnlyCollection(_requiredModulesSpecification)); + return _readonlyRequiredModulesSpecification ??= new ReadOnlyCollection(_requiredModulesSpecification); } } @@ -1303,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; @@ -1360,7 +1362,7 @@ internal void CaptureLocals() try { // Only copy simple mutable variables... - if (v.Options == ScopedItemOptions.None && !(v is NullVariable)) + if (v.Options == ScopedItemOptions.None && v is not NullVariable) { PSVariable newVar = new PSVariable(v.Name, v.Value, v.Options, v.Description); // The variable is already defined/set in the scope, and that means the attributes @@ -1438,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 015b2bb72d2..18e12541528 100644 --- a/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs +++ b/src/System.Management.Automation/engine/Modules/RemoteDiscoveryHelper.cs @@ -25,13 +25,13 @@ namespace System.Management.Automation { - internal class RemoteDiscoveryHelper + internal static class RemoteDiscoveryHelper { #region PSRP private static Collection RehydrateHashtableKeys(PSObject pso, string propertyName) { - var rehydrationFlags = DeserializingTypeConverter.RehydrationFlags.NullValueOk | + const DeserializingTypeConverter.RehydrationFlags rehydrationFlags = DeserializingTypeConverter.RehydrationFlags.NullValueOk | DeserializingTypeConverter.RehydrationFlags.MissingPropertyOk; Hashtable hashtable = DeserializingTypeConverter.GetPropertyValue(pso, propertyName, rehydrationFlags); if (hashtable == null) @@ -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); } @@ -53,7 +53,7 @@ private static Collection RehydrateHashtableKeys(PSObject pso, string pr internal static PSModuleInfo RehydratePSModuleInfo(PSObject deserializedModuleInfo) { - var rehydrationFlags = DeserializingTypeConverter.RehydrationFlags.NullValueOk | + const DeserializingTypeConverter.RehydrationFlags rehydrationFlags = DeserializingTypeConverter.RehydrationFlags.NullValueOk | DeserializingTypeConverter.RehydrationFlags.MissingPropertyOk; string name = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "Name", rehydrationFlags); string path = DeserializingTypeConverter.GetPropertyValue(deserializedModuleInfo, "Path", rehydrationFlags); @@ -112,24 +112,24 @@ internal static PSModuleInfo RehydratePSModuleInfo(PSObject deserializedModuleIn private static EventHandler GetStreamForwarder(Action forwardingAction, bool swallowInvalidOperationExceptions = false) { // TODO/FIXME: ETW event for extended semantics streams - return delegate (object sender, DataAddedEventArgs eventArgs) - { - var psDataCollection = (PSDataCollection)sender; - foreach (T t in psDataCollection.ReadAll()) - { - try - { - forwardingAction(t); - } - catch (InvalidOperationException) - { - if (!swallowInvalidOperationExceptions) - { - throw; - } - } - } - }; + return (object sender, DataAddedEventArgs eventArgs) => + { + var psDataCollection = (PSDataCollection)sender; + foreach (T t in psDataCollection.ReadAll()) + { + try + { + forwardingAction(t); + } + catch (InvalidOperationException) + { + if (!swallowInvalidOperationExceptions) + { + throw; + } + } + } + }; } // This is a static field (instead of a constant) to make it possible to set through tests (and/or by customers if needed for a workaround) @@ -137,10 +137,10 @@ private static EventHandler GetStreamForwarder(Action private static IEnumerable InvokeTopLevelPowerShell( PowerShell powerShell, - CancellationToken cancellationToken, PSCmdlet cmdlet, PSInvocationSettings invocationSettings, - string errorMessageTemplate) + string errorMessageTemplate, + CancellationToken cancellationToken) { using (var mergedOutput = new BlockingCollection>>(s_blockingCollectionCapacity)) { @@ -151,7 +151,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler errorHandler = GetStreamForwarder( errorRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { errorRecord = GetErrorRecordForRemotePipelineInvocation(errorRecord, errorMessageTemplate); HandleErrorFromPipeline(c, errorRecord, powerShell); @@ -161,7 +161,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler warningHandler = GetStreamForwarder( warningRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { c.WriteWarning(warningRecord.Message); return Enumerable.Empty(); @@ -170,7 +170,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler verboseHandler = GetStreamForwarder( verboseRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { c.WriteVerbose(verboseRecord.Message); return Enumerable.Empty(); @@ -179,7 +179,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler debugHandler = GetStreamForwarder( debugRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { c.WriteDebug(debugRecord.Message); return Enumerable.Empty(); @@ -188,7 +188,7 @@ private static IEnumerable InvokeTopLevelPowerShell( EventHandler informationHandler = GetStreamForwarder( informationRecord => mergedOutput.Add( - delegate (PSCmdlet c) + (PSCmdlet c) => { c.WriteInformation(informationRecord); return Enumerable.Empty(); @@ -256,13 +256,13 @@ private static IEnumerable InvokeTopLevelPowerShell( private static IEnumerable InvokeNestedPowerShell( PowerShell powerShell, - CancellationToken cancellationToken, PSCmdlet cmdlet, PSInvocationSettings invocationSettings, - string errorMessageTemplate) + string errorMessageTemplate, + CancellationToken cancellationToken) { EventHandler errorHandler = GetStreamForwarder( - delegate (ErrorRecord errorRecord) + (ErrorRecord errorRecord) => { errorRecord = GetErrorRecordForRemotePipelineInvocation(errorRecord, errorMessageTemplate); HandleErrorFromPipeline(cmdlet, errorRecord, powerShell); @@ -360,7 +360,7 @@ private static ErrorRecord GetErrorRecordForRemotePipelineInvocation(Exception i Exception outerException = new InvalidOperationException(errorMessage, innerException); RemoteException remoteException = innerException as RemoteException; - ErrorRecord remoteErrorRecord = remoteException != null ? remoteException.ErrorRecord : null; + ErrorRecord remoteErrorRecord = remoteException?.ErrorRecord; string errorId = remoteErrorRecord != null ? remoteErrorRecord.FullyQualifiedErrorId : innerException.GetType().Name; ErrorCategory errorCategory = remoteErrorRecord != null ? remoteErrorRecord.CategoryInfo.Category : ErrorCategory.NotSpecified; ErrorRecord errorRecord = new ErrorRecord(outerException, errorId, errorCategory, null); @@ -467,9 +467,9 @@ private static void HandleErrorFromPipeline(Cmdlet cmdlet, ErrorRecord errorReco internal static IEnumerable InvokePowerShell( PowerShell powerShell, - CancellationToken cancellationToken, PSCmdlet cmdlet, - string errorMessageTemplate) + string errorMessageTemplate, + CancellationToken cancellationToken) { CopyParameterFromCmdletToPowerShell(cmdlet, powerShell, "ErrorAction"); CopyParameterFromCmdletToPowerShell(cmdlet, powerShell, "WarningAction"); @@ -481,12 +481,12 @@ internal static IEnumerable InvokePowerShell( // TODO/FIXME: ETW events for the output stream IEnumerable outputStream = powerShell.IsNested - ? InvokeNestedPowerShell(powerShell, cancellationToken, cmdlet, invocationSettings, errorMessageTemplate) - : InvokeTopLevelPowerShell(powerShell, cancellationToken, cmdlet, invocationSettings, errorMessageTemplate); + ? InvokeNestedPowerShell(powerShell, cmdlet, invocationSettings, errorMessageTemplate, cancellationToken) + : InvokeTopLevelPowerShell(powerShell, cmdlet, invocationSettings, errorMessageTemplate, cancellationToken); return EnumerateWithCatch( outputStream, - delegate (Exception exception) + (Exception exception) => { ErrorRecord errorRecord = GetErrorRecordForRemotePipelineInvocation(exception, errorMessageTemplate); HandleErrorFromPipeline(cmdlet, errorRecord, powerShell); @@ -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; @@ -744,7 +744,7 @@ internal static IEnumerable GetCimModules( Cmdlet cmdlet, CancellationToken cancellationToken) { - moduleNamePatterns = moduleNamePatterns ?? new[] { "*" }; + moduleNamePatterns ??= new[] { "*" }; HashSet alreadyEmittedNamesOfCimModules = new HashSet(StringComparer.OrdinalIgnoreCase); IEnumerable remoteModules = moduleNamePatterns @@ -795,13 +795,13 @@ 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) { cimModules = cimModules.Select( - delegate (CimModule cimModule) + (CimModule cimModule) => { cimModule.FetchAllModuleFiles(cimSession, cimNamespace, options); return cimModule; @@ -810,14 +810,15 @@ private static IEnumerable GetCimModules( return EnumerateWithCatch( cimModules, - delegate (Exception exception) + (Exception exception) => { ErrorRecord errorRecord = GetErrorRecordForRemoteDiscoveryProvider(exception); if (!cmdlet.MyInvocation.ExpectingInput) { - if (((-1) != errorRecord.FullyQualifiedErrorId.IndexOf(DiscoveryProviderNotFoundErrorId, StringComparison.OrdinalIgnoreCase)) || - (cancellationToken.IsCancellationRequested || (exception is OperationCanceledException)) || - (!cimSession.TestConnection())) + if (errorRecord.FullyQualifiedErrorId.Contains(DiscoveryProviderNotFoundErrorId, StringComparison.OrdinalIgnoreCase) + || cancellationToken.IsCancellationRequested + || exception is OperationCanceledException + || !cimSession.TestConnection()) { cmdlet.ThrowTerminatingError(errorRecord); } @@ -855,9 +856,9 @@ internal static Hashtable RewriteManifest( IEnumerable typesToProcess, IEnumerable formatsToProcess) { - nestedModules = nestedModules ?? Array.Empty(); - typesToProcess = typesToProcess ?? Array.Empty(); - formatsToProcess = formatsToProcess ?? Array.Empty(); + nestedModules ??= Array.Empty(); + typesToProcess ??= Array.Empty(); + formatsToProcess ??= Array.Empty(); var newManifest = new Hashtable(StringComparer.OrdinalIgnoreCase); newManifest["NestedModules"] = nestedModules; @@ -980,8 +981,8 @@ internal static CimSession CreateCimSession( PSCredential credential, string authentication, bool isLocalHost, - CancellationToken cancellationToken, - PSCmdlet cmdlet) + PSCmdlet cmdlet, + CancellationToken cancellationToken) { if (isLocalHost) { @@ -1037,7 +1038,7 @@ internal static Hashtable ConvertCimModuleFileToManifestHashtable(RemoteDiscover internal static string GetModulePath(string remoteModuleName, Version remoteModuleVersion, string computerName, Runspace localRunspace) { - computerName = computerName ?? string.Empty; + computerName ??= string.Empty; string sanitizedRemoteModuleName = Regex.Replace(remoteModuleName, "[^a-zA-Z0-9]", string.Empty); string sanitizedComputerName = Regex.Replace(computerName, "[^a-zA-Z0-9]", string.Empty); diff --git a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs index 64958c9fde6..52fa4879f41 100644 --- a/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/RemoveModuleCommand.cs @@ -32,9 +32,9 @@ public sealed class RemoveModuleCommand : ModuleCmdletBase [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public string[] Name { - set { _name = value; } - get { return _name; } + + set { _name = value; } } private string[] _name = Array.Empty(); @@ -53,9 +53,9 @@ public string[] Name [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Cmdlets use arrays for parameters.")] public PSModuleInfo[] ModuleInfo { - set { _moduleInfo = value; } - get { return _moduleInfo; } + + set { _moduleInfo = value; } } private PSModuleInfo[] _moduleInfo = Array.Empty(); @@ -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 6ec64a4cf0f..892d7375387 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) @@ -41,7 +40,7 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) // So eat the invalid operation } - string scriptContent = ReadScript(path); + string scriptContent = File.ReadAllText(path, Encoding.Default); ParseError[] errors; var moduleAst = (new Parser()).Parse(path, scriptContent, null, out errors, ParseMode.ModuleAnalysis); @@ -90,25 +89,16 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) return result; } - internal static string ReadScript(string path) - { - using (FileStream readerStream = new FileStream(path, FileMode.Open, FileAccess.Read)) - { - Encoding defaultEncoding = ClrFacade.GetDefaultEncoding(); - Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle; - - using (StreamReader scriptReader = new StreamReader(readerStream, defaultEncoding)) - { - return scriptReader.ReadToEnd(); - } - } - } - internal List DiscoveredExports { get; set; } + internal Dictionary DiscoveredAliases { get; set; } + internal List DiscoveredModules { get; set; } + internal List DiscoveredCommandFilters { get; set; } + internal bool AddsSelfToPath { get; set; } + internal List DiscoveredClasses { get; set; } } @@ -160,11 +150,17 @@ static ExportVisitor() private readonly bool _forCompletion; internal List DiscoveredExports { get; set; } + internal List DiscoveredModules { get; set; } + internal Dictionary DiscoveredFunctions { get; set; } + internal Dictionary DiscoveredAliases { get; set; } + internal List DiscoveredCommandFilters { get; set; } + internal bool AddsSelfToPath { get; set; } + internal List DiscoveredClasses { get; set; } public override AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) @@ -268,10 +264,22 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a // - Exporting module members public override AstVisitAction VisitCommand(CommandAst commandAst) { - string commandName = - commandAst.GetCommandName() ?? - GetSafeValueVisitor.GetSafeValue(commandAst.CommandElements[0], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis) as string; + string commandName = commandAst.GetCommandName(); + if (commandName is null) + { + // GetCommandName only works if the name is a string constant. GetSafeValueVistor can evaluate some safe dynamic expressions + try + { + commandName = GetSafeValueVisitor.GetSafeValue(commandAst.CommandElements[0], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis) as string; + } + catch (ParseException) + { + // The script is invalid so we can't use GetSafeValue to get the name either. + } + } + // We couldn't get the name of the command. Either it's an anonymous scriptblock: & {"Some script"} + // Or it's a dynamic expression we couldn't safely resolve. if (commandName == null) return AstVisitAction.SkipChildren; @@ -335,10 +343,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) List commandsToPostFilter = new List(); - Action onEachCommand = importedCommandName => - { - commandsToPostFilter.Add(importedCommandName); - }; + Action onEachCommand = importedCommandName => commandsToPostFilter.Add(importedCommandName); // Process any exports from the module that we determine from // the -Function, -Cmdlet, or -Alias parameters @@ -425,9 +430,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) @@ -454,7 +462,7 @@ private void ProcessCmdletArguments(object value, Action onEachArgument) // // It also only populates the bound parameters for a limited set of parameters needed // for module analysis. - private Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string commandName) + private static Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string commandName) { var result = new Hashtable(StringComparer.OrdinalIgnoreCase); @@ -547,9 +555,9 @@ private Hashtable DoPsuedoParameterBinding(CommandAst commandAst, string command return result; } - private static Dictionary s_parameterBindingInfoTable; + private static readonly Dictionary s_parameterBindingInfoTable; - private class ParameterBindingInfo + private sealed class ParameterBindingInfo { internal ParameterInfo[] parameterInfo; } @@ -563,10 +571,10 @@ 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; } + internal List CommandsToPostFilter { 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 2f6c0fdd5cf..650588c32e4 100644 --- a/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs +++ b/src/System.Management.Automation/engine/Modules/TestModuleManifestCommand.cs @@ -172,7 +172,7 @@ protected override void ProcessRecord() && !IsValidGacAssembly(nestedModule.Name)) { Collection modules = GetModuleIfAvailable(nestedModule); - if (0 == modules.Count) + if (modules.Count == 0) { string errorMsg = StringUtil.Format(Modules.InvalidNestedModuleinModuleManifest, nestedModule.Name, filePath); var errorRecord = new ErrorRecord(new DirectoryNotFoundException(errorMsg), "Modules_InvalidNestedModuleinModuleManifest", @@ -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)) @@ -404,7 +406,7 @@ private bool IsValidFilePath(string path, PSModuleInfo module, bool verifyPathSc /// /// /// - private bool IsValidGacAssembly(string assemblyName) + private static bool IsValidGacAssembly(string assemblyName) { #if UNIX return false; @@ -420,23 +422,13 @@ private bool IsValidGacAssembly(string assemblyName) try { - var allFiles = Directory.GetFiles(gacPath, assemblyFile, SearchOption.AllDirectories); - - if (allFiles.Length == 0) - { - var allNgenFiles = Directory.GetFiles(gacPath, ngenAssemblyFile, SearchOption.AllDirectories); - if (allNgenFiles.Length == 0) - { - return false; - } - } + return Directory.EnumerateFiles(gacPath, assemblyFile, SearchOption.AllDirectories).Any() + || Directory.EnumerateFiles(gacPath, ngenAssemblyFile, SearchOption.AllDirectories).Any(); } catch { return false; } - - return true; #endif } } diff --git a/src/System.Management.Automation/engine/MshCmdlet.cs b/src/System.Management.Automation/engine/MshCmdlet.cs index d6c8d941a04..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,15 +64,17 @@ 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. /// - public struct SwitchParameter + public readonly struct SwitchParameter { - private bool _isPresent; + private readonly bool _isPresent; /// /// Returns true if the parameter was specified on the command line, false otherwise. /// @@ -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) { @@ -233,9 +237,9 @@ public override string ToString() /// public class CommandInvocationIntrinsics { - private ExecutionContext _context; - private PSCmdlet _cmdlet; - private MshCommandRuntime _commandRuntime; + private readonly ExecutionContext _context; + private readonly PSCmdlet _cmdlet; + private readonly MshCommandRuntime _commandRuntime; internal CommandInvocationIntrinsics(ExecutionContext context, PSCmdlet cmdlet) { @@ -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; } @@ -388,7 +391,7 @@ internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext contex SearchResolutionOptions.None, CommandTypes.Cmdlet, context); - do + while (true) { try { @@ -419,7 +422,7 @@ internal static CmdletInfo GetCmdlet(string commandName, ExecutionContext contex } current = ((IEnumerator)searcher).Current as CmdletInfo; - } while (true); + } return current; } @@ -497,7 +500,7 @@ public List GetCmdlets(string pattern) SearchResolutionOptions.CommandNameIsPattern, CommandTypes.Cmdlet, _context); - do + while (true) { try { @@ -530,7 +533,7 @@ public List GetCmdlets(string pattern) current = ((IEnumerator)searcher).Current as CmdletInfo; if (current != null) cmdlets.Add(current); - } while (true); + } return cmdlets; } @@ -631,7 +634,7 @@ internal IEnumerable GetCommands(string name, CommandTypes commandT searcher.CommandOrigin = commandOrigin.Value; } - do + while (true) { try { @@ -666,44 +669,50 @@ internal IEnumerable GetCommands(string name, CommandTypes commandT { yield return commandInfo; } - } while (true); + } } /// - /// 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; @@ -956,7 +975,7 @@ public string ParameterSetName /// /// If the cmdlet declares paging support (via ), /// then property contains arguments of the paging parameters. - /// Otherwise property is null. + /// Otherwise property is . /// public PagingParameters PagingParameters { @@ -999,7 +1018,7 @@ public CommandInvocationIntrinsics InvokeCommand { using (PSTransactionManager.GetEngineProtectionScope()) { - return _invokeCommand ?? (_invokeCommand = new CommandInvocationIntrinsics(Context, this)); + return _invokeCommand ??= new CommandInvocationIntrinsics(Context, this); } } } @@ -1009,4 +1028,3 @@ public CommandInvocationIntrinsics InvokeCommand } } - diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index 4d07cd3d3a8..e674cb1c5eb 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #pragma warning disable 1634, 1691 using System.Collections; @@ -91,8 +92,8 @@ internal bool IsPipelineInputExpected internal PipelineProcessor PipelineProcessor { get; set; } - private CommandInfo _commandInfo; - private InternalCommand _thisCommand; + private readonly CommandInfo _commandInfo; + private readonly InternalCommand _thisCommand; #endregion private_members @@ -124,7 +125,7 @@ public override string ToString() /// The invocation object for this command. internal InvocationInfo MyInvocation { - get { return _myInvocation ?? (_myInvocation = _thisCommand.MyInvocation); } + get { return _myInvocation ??= _thisCommand.MyInvocation; } } /// @@ -352,7 +353,7 @@ internal void WriteProgress(ProgressRecord progressRecord, bool overrideInquire) // WriteProgress. The following logic ensures that // there is a unique id for each Cmdlet instance. - if (0 == _sourceId) + if (_sourceId == 0) { _sourceId = Interlocked.Increment(ref s_lastUsedSourceId); } @@ -389,6 +390,9 @@ public void WriteProgress( WriteProgress(sourceId, progressRecord, false); } + internal bool IsWriteProgressEnabled() + => WriteHelper_ShouldWrite(ProgressPreference, lastProgressContinueStatus); + internal void WriteProgress( Int64 sourceId, ProgressRecord progressRecord, @@ -471,6 +475,9 @@ public void WriteDebug(string text) WriteDebug(new DebugRecord(text)); } + internal bool IsWriteDebugEnabled() + => WriteHelper_ShouldWrite(DebugPreference, lastDebugContinueStatus); + /// /// Display debug information. /// @@ -565,6 +572,9 @@ public void WriteVerbose(string text) WriteVerbose(new VerboseRecord(text)); } + internal bool IsWriteVerboseEnabled() + => WriteHelper_ShouldWrite(VerbosePreference, lastVerboseContinueStatus); + /// /// Display verbose information. /// @@ -659,6 +669,9 @@ public void WriteWarning(string text) WriteWarning(new WarningRecord(text)); } + internal bool IsWriteWarningEnabled() + => WriteHelper_ShouldWrite(WarningPreference, lastWarningContinueStatus); + /// /// Display warning information. /// @@ -732,6 +745,9 @@ public void WriteInformation(InformationRecord informationRecord) WriteInformation(informationRecord, false); } + internal bool IsWriteInformationEnabled() + => WriteHelper_ShouldWrite(InformationPreference, lastInformationContinueStatus); + /// /// Display tagged object information. /// @@ -875,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" /// /// /// @@ -927,7 +943,8 @@ private bool InitShouldLogPipelineExecutionDetail() /// internal string PipelineVariable { get; set; } - private PSVariable _pipelineVarReference = null; + private PSVariable _pipelineVarReference; + private bool _shouldRemovePipelineVariable; internal void SetupOutVariable() { @@ -940,20 +957,17 @@ 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; _outVarList = oldValue ?? new ArrayList(); - if (!(_thisCommand is PSScriptCmdlet)) + if (_thisCommand is not PSScriptCmdlet) { this.OutputPipe.AddVariableList(VariableStreamKind.Output, _outVarList); } @@ -971,30 +985,50 @@ 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 PSScriptCmdlet)) + if (_thisCommand is not PSScriptCmdlet) { this.OutputPipe.SetPipelineVariable(_pipelineVarReference); } } + 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. /// @@ -1062,8 +1096,8 @@ internal int OutBuffer /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype1")] /// public class RemoveMyObjectType1 : PSCmdlet @@ -1085,7 +1119,7 @@ internal int OutBuffer /// } /// } /// } - /// + /// /// /// /// @@ -1156,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 @@ -1179,7 +1213,7 @@ public bool ShouldProcess(string target) /// } /// } /// } - /// + /// /// /// /// @@ -1259,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 @@ -1276,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 @@ -1285,7 +1319,7 @@ public bool ShouldProcess(string target, string action) /// } /// } /// } - /// + /// /// /// /// @@ -1374,8 +1408,8 @@ public bool ShouldProcess( /// , /// /// - /// - /// namespace Microsoft.Samples.MSH.Cmdlet + /// + /// namespace Microsoft.Samples.Cmdlet /// { /// [Cmdlet(VerbsCommon.Remove,"myobjecttype3")] /// public class RemoveMyObjectType3 : PSCmdlet @@ -1392,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)) /// { @@ -1402,7 +1436,7 @@ public bool ShouldProcess( /// } /// } /// } - /// + /// /// /// /// @@ -1464,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. @@ -1680,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 @@ -1705,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")) /// ) /// { @@ -1724,7 +1758,7 @@ internal ShouldProcessPossibleOptimization CalculatePossibleShouldProcessOptimiz /// } /// } /// } - /// + /// /// /// /// @@ -1734,8 +1768,13 @@ public bool ShouldContinue(string query, string caption) { bool yesToAll = false; bool noToAll = false; - bool hasSecurityImpact = false; - return DoShouldContinue(query, caption, hasSecurityImpact, false, ref yesToAll, ref noToAll); + return DoShouldContinue( + query, + caption, + hasSecurityImpact: false, + supportsToAllOptions: false, + ref yesToAll, + ref noToAll); } /// @@ -1759,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. /// /// @@ -1806,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. /// /// @@ -1857,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 @@ -1885,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 @@ -1906,7 +1945,7 @@ public bool ShouldContinue( /// } /// } /// } - /// + /// /// /// /// @@ -2049,6 +2088,7 @@ public PSTransactionContext CurrentPSTransaction /// . /// etc. /// + [System.Diagnostics.CodeAnalysis.DoesNotReturn] public void ThrowTerminatingError(ErrorRecord errorRecord) { ThrowIfStopping(); @@ -2205,7 +2245,7 @@ internal void SetMergeFromRuntime(MshCommandRuntime fromRuntime) /// internal Pipe InputPipe { - get { return _inputPipe ?? (_inputPipe = new Pipe()); } + get { return _inputPipe ??= new Pipe(); } set { _inputPipe = value; } } @@ -2215,7 +2255,7 @@ internal Pipe InputPipe /// internal Pipe OutputPipe { - get { return _outputPipe ?? (_outputPipe = new Pipe()); } + get { return _outputPipe ??= new Pipe(); } set { _outputPipe = value; } } @@ -2238,7 +2278,7 @@ internal object[] GetResultsAsArray() /// internal Pipe ErrorOutputPipe { - get { return _errorOutputPipe ?? (_errorOutputPipe = new Pipe()); } + get { return _errorOutputPipe ??= new Pipe(); } set { _errorOutputPipe = value; } } @@ -2311,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. @@ -2320,8 +2360,7 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip { if (permittedToWrite == null) throw PSTraceSource.NewArgumentNullException(nameof(permittedToWrite)); - MshCommandRuntime mcr = permittedToWrite.commandRuntime as MshCommandRuntime; - if (mcr == null) + if (permittedToWrite.commandRuntime is not MshCommandRuntime mcr) throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime"); _pp = mcr.PipelineProcessor; if (_pp == null) @@ -2333,28 +2372,27 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip _pp._permittedToWriteToPipeline = permittedToWriteToPipeline; _pp._permittedToWriteThread = Thread.CurrentThread; } + /// - /// End the scope where WriteObject/WriteError is permitted. + /// Release all resources. /// - /// + /// + /// End the scope where WriteObject/WriteError is permitted. + /// public void Dispose() { _pp._permittedToWrite = _wasPermittedToWrite; _pp._permittedToWriteToPipeline = _wasPermittedToWriteToPipeline; _pp._permittedToWriteThread = _wasPermittedToWriteThread; - GC.SuppressFinalize(this); } // There is no finalizer, by design. This class relies on always // being disposed and always following stack semantics. - private PipelineProcessor _pp = null; - private InternalCommand _wasPermittedToWrite = null; - private bool _wasPermittedToWriteToPipeline = false; - private Thread _wasPermittedToWriteThread = null; + private readonly PipelineProcessor _pp = null; + private readonly InternalCommand _wasPermittedToWrite = null; + private readonly bool _wasPermittedToWriteToPipeline = false; + private readonly Thread _wasPermittedToWriteThread = null; } /// @@ -2371,10 +2409,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 @@ -2383,7 +2418,10 @@ public Exception ManageException(Exception e) // PipelineStoppedException should not get added to $Error // 2008/06/25 - narrieta: ExistNestedPromptException should not be added to $error either // 2019/10/18 - StopUpstreamCommandsException should not be added either - if (!(e is HaltCommandException) && !(e is PipelineStoppedException) && !(e is ExitNestedPromptException) && !(e is StopUpstreamCommandsException)) + if (e is not HaltCommandException + && e is not PipelineStoppedException + && e is not ExitNestedPromptException + && e is not StopUpstreamCommandsException) { try { @@ -2395,7 +2433,6 @@ public Exception ManageException(Exception e) } // Log a command health event - MshLog.LogCommandHealthEvent( Context, e, @@ -2534,8 +2571,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('+')) { @@ -2574,7 +2610,7 @@ internal void SetupVariable(VariableStreamKind streamKind, string variableName, varList = new ArrayList(); } - if (!(_thisCommand is PSScriptCmdlet)) + if (_thisCommand is not PSScriptCmdlet) { this.OutputPipe.AddVariableList(streamKind, varList); } @@ -2795,8 +2831,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. @@ -2810,7 +2854,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(); @@ -2825,66 +2869,68 @@ internal void _WriteErrorSkipAllowCheck(ErrorRecord errorRecord, ActionPreferenc Severity.Warning); } - this.PipelineProcessor.ExecutionFailed = true; if (LogPipelineExecutionDetail) { this.PipelineProcessor.LogExecutionError(_thisCommand.MyInvocation, errorRecord); } - ActionPreference preference = ErrorAction; - if (actionPreference.HasValue) + if (!isFromNativeStdError) { - preference = actionPreference.Value; - } + this.PipelineProcessor.ExecutionFailed = true; - // No trace of the error in the 'Ignore' case - if (ActionPreference.Ignore == preference) - { - return; // do not write or record to output pipe - } + ActionPreference preference = ErrorAction; + if (actionPreference.HasValue) + { + preference = actionPreference.Value; + } - // 2004/05/26-JonN - // The object is not written in the SilentlyContinue case - if (ActionPreference.SilentlyContinue == preference) - { - AppendErrorToVariables(errorRecord); - return; // do not write to output pipe - } + // No trace of the error in the 'Ignore' case + if (preference == ActionPreference.Ignore) + { + return; // do not write or record to output pipe + } - if (ContinueStatus.YesToAll == lastErrorContinueStatus) - { - preference = ActionPreference.Continue; - } + // 2004/05/26-JonN + // The object is not written in the SilentlyContinue case + if (preference == ActionPreference.SilentlyContinue) + { + AppendErrorToVariables(errorRecord); + return; // do not write to output pipe + } - switch (preference) - { - case ActionPreference.Stop: - ActionPreferenceStopException e = - new ActionPreferenceStopException( - MyInvocation, - errorRecord, - StringUtil.Format(CommandBaseStrings.ErrorPreferenceStop, - "ErrorActionPreference", - errorRecord.ToString())); - throw ManageException(e); + if (lastErrorContinueStatus == ContinueStatus.YesToAll) + { + preference = ActionPreference.Continue; + } - case ActionPreference.Inquire: - // ignore return value - // this will throw if the user chooses not to continue - lastErrorContinueStatus = InquireHelper( - RuntimeException.RetrieveMessage(errorRecord), - null, - true, // allowYesToAll - false, // allowNoToAll - true, // replaceNoWithHalt - false // hasSecurityImpact - ); - break; - } + switch (preference) + { + case ActionPreference.Stop: + ActionPreferenceStopException e = + new ActionPreferenceStopException( + MyInvocation, + errorRecord, + StringUtil.Format(CommandBaseStrings.ErrorPreferenceStop, + "ErrorActionPreference", + errorRecord.ToString())); + throw ManageException(e); + + case ActionPreference.Inquire: + // ignore return value + // this will throw if the user chooses not to continue + lastErrorContinueStatus = InquireHelper( + RuntimeException.RetrieveMessage(errorRecord), + null, + true, // allowYesToAll + false, // allowNoToAll + true, // replaceNoWithHalt + false // hasSecurityImpact + ); + break; + } - // 2005/01/20 Do not write the object to $error if - // ManageException has already done so - AppendErrorToVariables(errorRecord); + AppendErrorToVariables(errorRecord); + } // Add this note property and set its value to true for F&O // to decide whether to call WriteErrorLine or WriteLine. @@ -2894,7 +2940,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; } @@ -2945,21 +2991,19 @@ internal ConfirmImpact ConfirmPreference { // WhatIf not relevant, it never gets this far in that case if (Confirm) - return ConfirmImpact.Low; - if (Debug) { - if (IsConfirmFlagSet) // -Debug -Confirm:$false - return ConfirmImpact.None; return ConfirmImpact.Low; } - if (IsConfirmFlagSet) // -Confirm:$false + if (IsConfirmFlagSet) + { + // -Confirm:$false return ConfirmImpact.None; + } if (!_isConfirmPreferenceCached) { - bool defaultUsed = false; - _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed); + _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out _); _isConfirmPreferenceCached = true; } @@ -3021,7 +3065,7 @@ internal ActionPreference DebugPreference } } - private bool _isVerbosePreferenceCached = false; + private readonly bool _isVerbosePreferenceCached = false; private ActionPreference _verbosePreference = InitialSessionState.DefaultVerbosePreference; /// /// Preference setting. @@ -3070,7 +3114,7 @@ internal ActionPreference VerbosePreference internal bool IsWarningActionSet { get; private set; } = false; - private bool _isWarningPreferenceCached = false; + private readonly bool _isWarningPreferenceCached = false; private ActionPreference _warningPreference = InitialSessionState.DefaultWarningPreference; /// /// Preference setting. @@ -3126,7 +3170,10 @@ internal ActionPreference WarningPreference /// internal bool Verbose { - get { return _verboseFlag; } + get + { + return _verboseFlag; + } set { @@ -3191,7 +3238,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. /// /// @@ -3199,7 +3246,10 @@ internal SwitchParameter UseTransaction /// internal bool Debug { - get { return _debugFlag; } + get + { + return _debugFlag; + } set { @@ -3225,8 +3275,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; } @@ -3293,7 +3342,7 @@ internal ActionPreference ProgressPreference { get { - if (_isProgressPreferenceSet) + if (IsProgressActionSet) return _progressPreference; if (!_isProgressPreferenceCached) @@ -3314,12 +3363,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; /// @@ -3379,7 +3430,7 @@ internal enum ContinueStatus No, YesToAll, NoToAll - }; + } internal ContinueStatus lastShouldProcessContinueStatus = ContinueStatus.Yes; internal ContinueStatus lastErrorContinueStatus = ContinueStatus.Yes; @@ -3620,7 +3671,7 @@ bool hasSecurityImpact inquireCaption = CommandBaseStrings.InquireCaptionDefault; } - do + while (true) { // Transcribe the confirmation message CBhost.InternalUI.TranscribeResult(inquireCaption); @@ -3679,7 +3730,7 @@ bool hasSecurityImpact CBhost.EnterNestedPrompt(_thisCommand); // continue loop } - else if (-1 == response) + else if (response == -1) { ActionPreferenceStopException e = new ActionPreferenceStopException( @@ -3694,7 +3745,7 @@ bool hasSecurityImpact PSTraceSource.NewInvalidOperationException(); throw ManageException(e); } - } while (true); + } } /// @@ -3719,8 +3770,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); } @@ -3747,9 +3800,7 @@ internal void SetVariableListsInPipe() 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); } @@ -3772,9 +3823,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 451048e2018..4bde286d165 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -6,6 +6,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation.Internal; @@ -27,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 { /// @@ -119,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 { /// @@ -147,7 +148,7 @@ public enum PSMemberViewTypes /// /// Match options. /// - [FlagsAttribute] + [Flags] internal enum MshMemberMatchOptions { /// @@ -161,7 +162,7 @@ internal enum MshMemberMatchOptions IncludeHidden = 1, /// - /// Only include members with property set to true + /// Only include members with property set to /// OnlySerializable = 2 } @@ -183,7 +184,7 @@ internal virtual void ReplicateInstance(object particularInstance) internal void SetValueNoConversion(object setValue) { - if (!(this is PSProperty thisAsProperty)) + if (this is not PSProperty thisAsProperty) { this.Value = setValue; return; @@ -283,12 +284,12 @@ protected void SetMemberName(string name) internal bool MatchesOptions(MshMemberMatchOptions options) { - if (this.IsHidden && (0 == (options & MshMemberMatchOptions.IncludeHidden))) + if (this.IsHidden && ((options & MshMemberMatchOptions.IncludeHidden) == 0)) { return false; } - if (!this.ShouldSerialize && (0 != (options & MshMemberMatchOptions.OnlySerializable))) + if (!this.ShouldSerialize && ((options & MshMemberMatchOptions.OnlySerializable) != 0)) { return false; } @@ -356,9 +357,9 @@ public override string ToString() returnValue.Append(" = "); if (ConversionType != null) { - returnValue.Append("("); + returnValue.Append('('); returnValue.Append(ConversionType); - returnValue.Append(")"); + returnValue.Append(')'); } returnValue.Append(ReferencedMemberName); @@ -426,7 +427,7 @@ public PSAliasProperty(string name, string referencedMemberName, Type conversion internal PSMemberInfo ReferencedMember => this.LookupMember(ReferencedMemberName); /// - /// Gets the the type to convert the referenced member's value. It might be + /// Gets the type to convert the referenced member's value. It might be /// null when no conversion is done. /// public Type ConversionType { get; private set; } @@ -552,7 +553,7 @@ private void LookupMember(string name, HashSet visitedAliases, out PSMem name); } - if (!(member is PSAliasProperty aliasMember)) + if (member is not PSAliasProperty aliasMember) { hasCycle = false; returnedMember = member; @@ -616,24 +617,24 @@ public override string ToString() { StringBuilder returnValue = new StringBuilder(); returnValue.Append(this.TypeNameOfValue); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(this.Name); - returnValue.Append("{"); + returnValue.Append('{'); if (this.IsGettable) { returnValue.Append("get="); returnValue.Append(GetterCodeReference.Name); - returnValue.Append(";"); + returnValue.Append(';'); } if (this.IsSettable) { returnValue.Append("set="); returnValue.Append(SetterCodeReference.Name); - returnValue.Append(";"); + returnValue.Append(';'); } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -875,6 +876,7 @@ public override PSMemberInfo Copy() /// /// When getting and there is no getter or when the getter throws an exception. /// When setting and there is no setter or when the setter throws an exception. + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "")] public override object Value { get @@ -964,6 +966,7 @@ public override object Value /// Gets the type of the value for this member. /// /// If there is no property getter. + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "")] public override string TypeNameOfValue { get @@ -1274,9 +1277,9 @@ public override string ToString() StringBuilder returnValue = new StringBuilder(); returnValue.Append(GetDisplayTypeNameOfValue(this.Value)); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(this.Name); - returnValue.Append("="); + returnValue.Append('='); returnValue.Append(this.noteValue == null ? "null" : this.noteValue.ToString()); return returnValue.ToString(); } @@ -1421,9 +1424,9 @@ public override string ToString() { StringBuilder returnValue = new StringBuilder(); returnValue.Append(GetDisplayTypeNameOfValue(_variable.Value)); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(_variable.Name); - returnValue.Append("="); + returnValue.Append('='); returnValue.Append(_variable.Value ?? "null"); return returnValue.ToString(); } @@ -1541,24 +1544,24 @@ public override string ToString() { StringBuilder returnValue = new StringBuilder(); returnValue.Append(this.TypeNameOfValue); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(this.Name); returnValue.Append(" {"); if (this.IsGettable) { returnValue.Append("get="); returnValue.Append(this.GetterScript.ToString()); - returnValue.Append(";"); + returnValue.Append(';'); } if (this.IsSettable) { returnValue.Append("set="); returnValue.Append(this.SetterScript.ToString()); - returnValue.Append(";"); + returnValue.Append(';'); } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -1786,6 +1789,7 @@ public override PSMemberInfo Copy() /// /// When setting and there is no setter, /// when the setter throws an exception or when there is no Runspace to run the script. + [SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "")] public override object Value { get @@ -1905,22 +1909,34 @@ 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; } /// - /// If null then there are no constraints + /// If then there are no constraints /// public Type MethodTargetType { get; } /// - /// If null then there are no constraints + /// 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) { @@ -1942,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) @@ -1961,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; } @@ -1971,7 +1990,7 @@ public bool Equals(PSMethodInvocationConstraints other) public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) + if (obj is null) { return false; } @@ -1990,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)); @@ -2240,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); @@ -2283,7 +2316,7 @@ public override string ToString() { StringBuilder returnValue = new StringBuilder(); returnValue.Append(this.TypeNameOfValue); - returnValue.Append(" "); + returnValue.Append(' '); returnValue.Append(this.Name); returnValue.Append("();"); return returnValue.ToString(); @@ -2596,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); } @@ -2655,7 +2685,7 @@ private static Type GetMethodGroupType(MethodInfo methodInfo) return DelegateHelpers.MakeDelegate(methodTypes); } - catch (TypeLoadException) + catch (Exception) { return typeof(Func); } @@ -3038,7 +3068,7 @@ public override string ToString() } returnValue.Insert(0, this.Name); - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -3335,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; } } @@ -3441,7 +3470,7 @@ public override string ToString() returnValue.Remove(returnValue.Length - 2, 2); } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -3554,7 +3583,7 @@ public override string ToString() StringBuilder eventDefinition = new StringBuilder(); eventDefinition.Append(this.baseEvent.ToString()); - eventDefinition.Append("("); + eventDefinition.Append('('); int loopCounter = 0; foreach (ParameterInfo parameter in baseEvent.EventHandlerType.GetMethod("Invoke").GetParameters()) @@ -3567,7 +3596,7 @@ public override string ToString() loopCounter++; } - eventDefinition.Append(")"); + eventDefinition.Append(')'); return eventDefinition.ToString(); } @@ -3666,7 +3695,7 @@ public override PSMemberInfo Copy() /// /// /// This class is used in PSMemberInfoInternalCollection and ReadOnlyPSMemberInfoCollection. /// - internal class MemberMatch + internal static class MemberMatch { internal static WildcardPattern GetNamePattern(string name) { @@ -3728,7 +3757,7 @@ internal static PSMemberInfoInternalCollection Match(PSMemberInfoInternalC /// A Predicate that determine if a member name matches a criterion. /// /// - /// true if the matches the predicate, otherwise false. + /// if the matches the predicate, otherwise . public delegate bool MemberNamePredicate(string memberName); /// @@ -4970,7 +4999,7 @@ internal override ReadOnlyPSMemberInfoCollection Match(string name, PSMemberT /// The enumerator for this collection. public override IEnumerator GetEnumerator() { - return new Enumerator(this); + return new Enumerator(this); } internal override T FirstOrDefault(MemberNamePredicate predicate) @@ -5024,17 +5053,17 @@ internal override T FirstOrDefault(MemberNamePredicate predicate) /// /// Enumerable for this class. /// - internal struct Enumerator : IEnumerator where S : PSMemberInfo + internal struct Enumerator : IEnumerator { - private S _current; + private T _current; private int _currentIndex; - private readonly PSMemberInfoInternalCollection _allMembers; + 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) + internal Enumerator(PSMemberInfoIntegratingCollection integratingCollection) { using (PSObject.MemberResolution.TraceScope("Enumeration Start")) { @@ -5059,14 +5088,14 @@ 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() { _currentIndex++; - S member = null; + T member = null; while (_currentIndex < _allMembers.Count) { member = _allMembers[_currentIndex]; @@ -5089,10 +5118,10 @@ public bool MoveNext() } /// - /// Current PSMemberInfo in the enumeration. + /// Gets the current PSMemberInfo in the enumeration. /// /// For invalid arguments. - S IEnumerator.Current + T IEnumerator.Current { get { @@ -5105,7 +5134,7 @@ S IEnumerator.Current } } - object IEnumerator.Current => ((IEnumerator)this).Current; + object IEnumerator.Current => ((IEnumerator)this).Current; void IEnumerator.Reset() { diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index cd7a8cf0a14..c5e8bbac443 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 @@ -164,20 +163,21 @@ private static T AdapterGetFirstMemberOrDefaultDelegate(PSObject msjObj, Memb return retValue; } - internal static PSMemberInfoInternalCollection TransformMemberInfoCollection(PSMemberInfoCollection source) where T : PSMemberInfo where U : PSMemberInfo + internal static PSMemberInfoInternalCollection TransformMemberInfoCollection(PSMemberInfoCollection source) + where TSource : PSMemberInfo where TResult : PSMemberInfo { - if (typeof(T) == typeof(U)) + if (typeof(TSource) == typeof(TResult)) { // If the types are the same, don't make a copy, return the cached collection. - return source as PSMemberInfoInternalCollection; + return source as PSMemberInfoInternalCollection; } - PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); - foreach (T member in source) + PSMemberInfoInternalCollection returnValue = new PSMemberInfoInternalCollection(); + foreach (TSource member in source) { - if (member is U tAsU) + if (member is TResult result) { - returnValue.Add(tAsU); + returnValue.Add(result); } } @@ -489,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. @@ -525,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); @@ -588,9 +592,7 @@ protected PSObject(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - string serializedData = info.GetValue("CliXml", typeof(string)) as string; - - if (serializedData == null) + if (info.GetValue("CliXml", typeof(string)) is not string serializedData) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } @@ -653,6 +655,7 @@ internal static PSObject ConstructPSObjectFromSerializationInfo(SerializationInf new PSObject.AdapterSet(new ThirdPartyAdapter(typeof(Microsoft.Management.Infrastructure.CimInstance), new Microsoft.PowerShell.Cim.CimInstanceAdapter()), PSObject.DotNetInstanceAdapter); + #if !UNIX private static readonly AdapterSet s_managementObjectAdapter = new AdapterSet(new ManagementObjectAdapter(), DotNetInstanceAdapter); private static readonly AdapterSet s_managementClassAdapter = new AdapterSet(new ManagementClassApdapter(), DotNetInstanceAdapter); @@ -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); } } @@ -948,6 +936,7 @@ public static implicit operator PSObject(int valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// /// /// @@ -956,6 +945,7 @@ public static implicit operator PSObject(string valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// /// /// @@ -964,6 +954,7 @@ public static implicit operator PSObject(Hashtable valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// /// /// @@ -972,6 +963,7 @@ public static implicit operator PSObject(double valueToConvert) { return PSObject.AsPSObject(valueToConvert); } + /// /// /// @@ -988,8 +980,7 @@ public static implicit operator PSObject(bool valueToConvert) /// internal static object Base(object obj) { - PSObject mshObj = obj as PSObject; - if (mshObj == null) + if (obj is not PSObject mshObj) { return obj; } @@ -1073,8 +1064,7 @@ internal static PSObject AsPSObject(object obj, bool storeTypeNameAndInstanceMem /// internal static object GetKeyForResurrectionTables(object obj) { - var pso = obj as PSObject; - if (pso == null) + if (obj is not PSObject pso) { return obj; } @@ -1175,7 +1165,7 @@ private static string ToStringEmptyBaseObject(ExecutionContext context, PSObject isFirst = false; returnValue.Append(property.Name); - returnValue.Append("="); + returnValue.Append('='); // Don't evaluate script properties during a ToString() operation. var propertyValue = property is PSScriptProperty ? property.GetType().FullName : property.Value; @@ -1188,7 +1178,7 @@ private static string ToStringEmptyBaseObject(ExecutionContext context, PSObject return string.Empty; } - returnValue.Append("}"); + returnValue.Append('}'); return returnValue.ToString(); } @@ -1621,7 +1611,7 @@ public virtual PSObject Copy() bool needToReAddInstanceMembersAndTypeNames = !object.ReferenceEquals(GetKeyForResurrectionTables(this), GetKeyForResurrectionTables(returnValue)); if (needToReAddInstanceMembersAndTypeNames) { - Diagnostics.Assert(!returnValue.InstanceMembers.Any(), "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); + Diagnostics.Assert(returnValue.InstanceMembers.Count == 0, "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); foreach (PSMemberInfo member in this.InstanceMembers) { if (member.IsHidden) @@ -1861,8 +1851,7 @@ internal static object GetNoteSettingValue(PSMemberSet settings, string noteName settings.ReplicateInstance(ownerObject); } - PSNoteProperty note = settings.Members[noteName] as PSNoteProperty; - if (note == null) + if (settings.Members[noteName] is not PSNoteProperty note) { return defaultValue; } @@ -2058,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"); @@ -2150,7 +2139,7 @@ public override IEnumerable GetDynamicMemberNames() private bool MustDeferIDMOP() { var baseObject = PSObject.Base(Value); - return baseObject is IDynamicMetaObjectProvider && !(baseObject is PSObject); + return baseObject is IDynamicMetaObjectProvider && baseObject is not PSObject; } private DynamicMetaObject DeferForIDMOP(DynamicMetaObjectBinder binder, params DynamicMetaObject[] args) @@ -2538,13 +2527,13 @@ internal static string Type(Type type, bool dropNamespaces = false, string key = { string elementDefinition = Type(type.GetElementType(), dropNamespaces); var sb = new StringBuilder(elementDefinition, elementDefinition.Length + 10); - sb.Append("["); + sb.Append('['); for (int i = 0; i < type.GetArrayRank() - 1; ++i) { - sb.Append(","); + sb.Append(','); } - sb.Append("]"); + sb.Append(']'); result = sb.ToString(); } else diff --git a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs index ee54859a1ea..113161748c9 100644 --- a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs +++ b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs @@ -31,7 +31,7 @@ public class SettingValueExceptionEventArgs : EventArgs public Exception Exception { get; } /// - /// Initializes a new instance of setting the value of of the exception that triggered the associated event. + /// Initializes a new instance of setting the value of the exception that triggered the associated event. /// /// Exception that triggered the associated event. internal SettingValueExceptionEventArgs(Exception exception) @@ -64,7 +64,7 @@ public class GettingValueExceptionEventArgs : EventArgs public Exception Exception { get; } /// - /// Initializes a new instance of setting the value of of the exception that triggered the associated event. + /// Initializes a new instance of setting the value of the exception that triggered the associated event. /// /// Exception that triggered the associated event. internal GettingValueExceptionEventArgs(Exception exception) @@ -92,6 +92,7 @@ internal GettingValueExceptionEventArgs(Exception exception) public class PSObjectPropertyDescriptor : PropertyDescriptor { internal event EventHandler SettingValueException; + internal event EventHandler GettingValueException; internal PSObjectPropertyDescriptor(string propertyName, Type propertyType, bool isReadOnly, AttributeCollection propertyAttributes) @@ -218,13 +219,12 @@ private static PSObject GetComponentPSObject(object component) PSObject mshObj = component as PSObject; if (mshObj == null) { - PSObjectTypeDescriptor descriptor = component as PSObjectTypeDescriptor; - if (descriptor == null) + if (component is not PSObjectTypeDescriptor descriptor) { throw PSTraceSource.NewArgumentException(nameof(component), ExtendedTypeSystem.InvalidComponent, "component", - typeof(PSObject).Name, - typeof(PSObjectTypeDescriptor).Name); + nameof(PSObject), + nameof(PSObjectTypeDescriptor)); } mshObj = descriptor.Instance; @@ -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); @@ -467,8 +464,7 @@ public override PropertyDescriptorCollection GetProperties(Attribute[] attribute /// True if the Instance property of is equal to the current Instance; otherwise, false. public override bool Equals(object obj) { - PSObjectTypeDescriptor other = obj as PSObjectTypeDescriptor; - if (other == null) + if (obj is not PSObjectTypeDescriptor other) { return false; } @@ -771,4 +767,3 @@ public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object } } } - diff --git a/src/System.Management.Automation/engine/MshReference.cs b/src/System.Management.Automation/engine/MshReference.cs index 05077708608..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: @@ -88,4 +88,3 @@ public PSReference(object value) : base(value) } } } - diff --git a/src/System.Management.Automation/engine/MshSecurityException.cs b/src/System.Management.Automation/engine/MshSecurityException.cs index 878de10e2b4..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; } @@ -118,7 +106,6 @@ public override string Message get { return _message; } } - private string _message; + private readonly string _message; } } - diff --git a/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs b/src/System.Management.Automation/engine/MshSnapinQualifiedName.cs index b23df2202eb..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); @@ -132,4 +132,3 @@ public override string ToString() } } } - 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 0091596a443..31ac506c86a 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,149 @@ 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 && ExpandTilde(arg, parameter)) { - arg = ResolvePath(arg, Context); + 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 +473,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,23 +488,32 @@ 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 " , "; } /// /// The native command to bind to. /// - private NativeCommand _nativeCommand; - private static readonly string TildeDirectorySeparator = $"~{Path.DirectorySeparatorChar}"; - private static readonly string TildeAltDirectorySeparator = $"~{Path.AltDirectorySeparatorChar}"; + private readonly NativeCommand _nativeCommand; #endregion private members } diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinderController.cs index 5acdf199637..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, @@ -83,4 +104,3 @@ internal override Collection BindParameters(Collection private static readonly Collection s_emptyReturnCollection = new Collection(); } } - diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 3a12dab6295..145fe968fda 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -3,22 +3,26 @@ #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.Linq; +using System.Management.Automation.Internal; +using System.Runtime.CompilerServices; +using System.Runtime.Serialization; using System.Text; -using System.Collections; using System.Threading; -using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; +using System.Threading.Tasks; using System.Xml; -using System.Runtime.InteropServices; +using Microsoft.PowerShell.Commands; +using Microsoft.PowerShell.Telemetry; +using Microsoft.Win32; 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 { @@ -33,7 +37,7 @@ internal enum NativeCommandIOFormat { Text, Xml - }; + } /// /// Different streams produced by minishell output. @@ -131,18 +135,280 @@ 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 HashSet s_legacyFileExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".js", + ".wsf", + ".cmd", + ".bat", + ".vbs", + }; + + /// + /// This is the list of native commands that have non-standard behavior with regard to argument passing. + /// We use Legacy argument parsing for them when 'PSNativeCommandArgumentPassing' is set to "Windows". + /// + private static readonly HashSet s_legacyCommands = new(StringComparer.OrdinalIgnoreCase) + { + "cmd", + "cscript", + "find", + "sqlcmd", + "wscript", + }; + +#if !UNIX + /// + /// List of known package managers pulled from the registry. + /// + private static readonly HashSet s_knownPackageManagers = GetPackageManagerListFromRegistry(); + + /// + /// Indicates whether the Path Update feature is enabled in a given session. + /// PowerShell sessions could reuse the same thread, so we cannot cache the value with a thread static variable. + /// + private static readonly ConditionalWeakTable s_pathUpdateFeatureEnabled = new(); + + private readonly bool _isPackageManager; + private string _originalUserEnvPath; + private string _originalSystemEnvPath; + + /// + /// Gets the known package managers from the registry. + /// + private static HashSet GetPackageManagerListFromRegistry() + { + // We only account for the first 8 package managers. This is the same behavior as in CMD. + const int MaxPackageManagerCount = 8; + const string RegKeyPath = @"Software\Microsoft\Command Processor\KnownPackageManagers"; + + string[] subKeyNames = null; + HashSet retSet = null; + + try + { + using RegistryKey key = Registry.LocalMachine.OpenSubKey(RegKeyPath); + subKeyNames = key?.GetSubKeyNames(); + } + catch + { + return null; + } + + if (subKeyNames is { Length: > 0 }) + { + IEnumerable names = subKeyNames.Length <= MaxPackageManagerCount + ? subKeyNames + : subKeyNames.Take(MaxPackageManagerCount); + + retSet = new(names, StringComparer.OrdinalIgnoreCase); + } + + return retSet; + } + + /// + /// Check if the given name is a known package manager from the registry list. + /// + private static bool IsKnownPackageManager(string name) + { + if (s_knownPackageManagers is null) + { + return false; + } + + if (s_knownPackageManagers.Contains(name)) + { + return true; + } + + int lastDotIndex = name.LastIndexOf('.'); + if (lastDotIndex > 0) + { + string nameWithoutExt = name[..lastDotIndex]; + if (s_knownPackageManagers.Contains(nameWithoutExt)) + { + return true; + } + } + + return false; + } + + /// + /// Check if the Path Update feature is enabled for the given session. + /// + private static bool IsPathUpdateFeatureEnabled(ExecutionContext context) + { + // We check only once per session. + if (s_pathUpdateFeatureEnabled.TryGetValue(context, out string value)) + { + // The feature is enabled if the value is not null. + return value is { }; + } + + // Disable Path Update if 'EnvironmentProvider' is disabled in the current session, or the current session is restricted. + bool enabled = context.EngineSessionState.Providers.ContainsKey(EnvironmentProvider.ProviderName) + && !Utils.IsSessionRestricted(context); + + // - Use the static empty string instance to indicate that the feature is enabled. + // - Use the null value to indicate that the feature is disabled. + s_pathUpdateFeatureEnabled.TryAdd(context, enabled ? string.Empty : null); + return enabled; + } + + /// + /// Gets the added part of the new string compared to the old string. + /// + private static ReadOnlySpan GetAddedPartOfString(string oldString, string newString) + { + if (oldString.Length >= newString.Length) + { + // Nothing added or something removed. + return ReadOnlySpan.Empty; + } + + int index = newString.IndexOf(oldString); + if (index is -1) + { + // The new and old strings are drastically different. Stop trying in this case. + return ReadOnlySpan.Empty; + } + + if (index > 0) + { + // Found the old string at non-zero offset, so something was prepended to the old string. + return newString.AsSpan(0, index); + } + else + { + // Found the old string at the beginning of the new string, so something was appended to the old string. + return newString.AsSpan(oldString.Length); + } + } + + /// + /// Update the process-scope environment variable Path based on the changes in the user-scope and system-scope Path. + /// + /// The old value of the user-scope Path retrieved from registry. + /// The old value of the system-scope Path retrieved from registry. + private static void UpdateProcessEnvPath(string oldUserPath, string oldSystemPath) + { + string newUserEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User); + string newSystemEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + string procEnvPath = Environment.GetEnvironmentVariable("Path"); + + ReadOnlySpan userPathChange = GetAddedPartOfString(oldUserPath, newUserEnvPath).Trim(';'); + ReadOnlySpan systemPathChange = GetAddedPartOfString(oldSystemPath, newSystemEnvPath).Trim(';'); + + // Add 2 to account for the path separators we may need to add. + int maxLength = procEnvPath.Length + userPathChange.Length + systemPathChange.Length + 2; + StringBuilder newPath = null; + + if (userPathChange.Length > 0) + { + CreateNewProcEnvPath(userPathChange); + } + + if (systemPathChange.Length > 0) + { + CreateNewProcEnvPath(systemPathChange); + } + + if (newPath is { Length: > 0 }) + { + // Update the process env Path. + Environment.SetEnvironmentVariable("Path", newPath.ToString()); + } + + // Helper method to create a new env Path string. + void CreateNewProcEnvPath(ReadOnlySpan newChange) + { + newPath ??= new StringBuilder(procEnvPath, capacity: maxLength); + + if (newPath.Length is 0 || newPath[^1] is ';') + { + newPath.Append(newChange); + } + else + { + newPath.Append(';').Append(newChange); + } + } + } +#endif + #region ctor/native command properties /// /// Information about application which is invoked by this instance of /// NativeCommandProcessor. /// - private ApplicationInfo _applicationInfo; + private readonly ApplicationInfo _applicationInfo; /// /// Initializes the new instance of NativeCommandProcessor class. @@ -182,7 +448,11 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex // Create input writer for providing input to the process. _inputWriter = new ProcessInputWriter(Command); - _isTranscribing = this.Command.Context.EngineHostInterface.UI.IsTranscribing; + _isTranscribing = context.EngineHostInterface.UI.IsTranscribing; + +#if !UNIX + _isPackageManager = IsKnownPackageManager(_applicationInfo.Name) && IsPathUpdateFeatureEnabled(context); +#endif } /// @@ -222,16 +492,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. /// @@ -248,8 +518,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 = @@ -288,8 +556,6 @@ internal NativeCommandParameterBinderController NativeParameterBinderController /// internal override void Prepare(IDictionary psDefaultParameterValues) { - _isPreparedCalled = true; - // Check if the application is minishell _isMiniShell = IsMiniShell(); @@ -307,7 +573,7 @@ internal override void Prepare(IDictionary psDefaultParameterValues) catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } @@ -319,9 +585,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); @@ -329,7 +600,7 @@ internal override void ProcessRecord() catch (Exception) { // Do cleanup in case of exception - CleanUp(); + CleanUp(killBackgroundProcess: true); throw; } } @@ -337,12 +608,12 @@ internal override void ProcessRecord() /// /// Process object for the invoked application. /// - private System.Diagnostics.Process _nativeProcess; + private Process _nativeProcess; /// /// This is used for writing input to the process. /// - private ProcessInputWriter _inputWriter = null; + private readonly ProcessInputWriter _inputWriter = null; /// /// Is true if this command is to be run "standalone" - that is, with @@ -358,7 +629,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; @@ -370,14 +641,67 @@ internal override void ProcessRecord() private BlockingCollection _nativeProcessOutputQueue; private static bool? s_supportScreenScrape = null; - private bool _isTranscribing; + private readonly bool _isTranscribing; private Host.Coordinates _startPosition; /// /// Object used for synchronization between StopProcessing thread and /// Pipeline thread. /// - private object _sync = new object(); + 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. @@ -401,7 +725,8 @@ private void InitNativeProcess() _startPosition = new Host.Coordinates(); - CalculateIORedirection(out redirectOutput, out redirectError, out redirectInput); + bool isWindowsApplication = IsWindowsApplication(this.Path); + CalculateIORedirection(isWindowsApplication, out redirectOutput, out redirectError, out redirectInput); // Find out if it's the only command in the pipeline. bool soloCommand = this.Command.MyInvocation.PipelineLength == 1; @@ -409,6 +734,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")) @@ -420,6 +750,12 @@ private void InitNativeProcess() // must set UseShellExecute to false if we modify the environment block startInfo.UseShellExecute = false; } + + if (_isPackageManager) + { + _originalUserEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User); + _originalSystemEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + } #endif if (this.Command.Context.CurrentPipelineStopping) @@ -430,17 +766,16 @@ 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 && (true == s_supportScreenScrape)) + if (_isTranscribing && (s_supportScreenScrape == true)) { _startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition; _startPosition.X = 0; @@ -467,16 +802,24 @@ 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)) { - if (IsConsoleApplication(executable)) + isWindowsApplication = IsWindowsApplication(executable); + if (!isWindowsApplication) { // Allocate a console if there isn't one attached already... ConsoleVisibility.AllocateHiddenConsole(); @@ -484,7 +827,17 @@ 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) + { + // codeql[cs/microsoft/command-line-injection] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method and the path portion of the argument is escaped. The user assumes trust for the file path specified on the user's system to start process for, and in the case of remoting, restricted remoting security guidelines should be used. + startInfo.Arguments = "\"" + oldFileName + "\" " + startInfo.Arguments; + } + else + { + startInfo.ArgumentList.Insert(0, oldFileName); + } startInfo.FileName = executable; try { @@ -494,7 +847,16 @@ 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); + } + + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. startInfo.FileName = oldFileName; } } @@ -503,7 +865,7 @@ private void InitNativeProcess() // we will try launching one last time using ShellExecute... if (notDone) { - if (soloCommand && startInfo.UseShellExecute == false) + if (soloCommand && !startInfo.UseShellExecute) { startInfo.UseShellExecute = true; startInfo.RedirectStandardInput = false; @@ -516,6 +878,13 @@ private void InitNativeProcess() throw; } } +#endif + } + + if (UpstreamIsNativeCommand) + { + _processInitialized ??= new SemaphoreSlim(0, 1); + _processInitialized.Release(); } } @@ -530,9 +899,9 @@ private void InitNativeProcess() else { _isRunningInBackground = true; - if (startInfo.UseShellExecute == false) + if (!startInfo.UseShellExecute) { - _isRunningInBackground = IsWindowsApplication(_nativeProcess.StartInfo.FileName); + _isRunningInBackground = isWindowsApplication; } } @@ -549,7 +918,7 @@ private void InitNativeProcess() lock (_sync) { - if (!_stopped) + if (!_stopped && !UpstreamIsNativeCommand) { _inputWriter.Start(_nativeProcess, inputFormat); } @@ -562,7 +931,7 @@ private void InitNativeProcess() throw; } - if (_isRunningInBackground == false) + if (!_isRunningInBackground) { InitOutputQueue(); } @@ -598,6 +967,8 @@ private void InitNativeProcess() } } + private AsyncByteStreamTransfer _stdOutByteTransfer; + private void InitOutputQueue() { // if output is redirected, start reading output of process in queue. @@ -607,9 +978,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); } } } @@ -640,12 +1034,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; } /// @@ -653,21 +1044,38 @@ private ProcessOutputObject DequeueProcessOutput(bool blocking) /// private void ConsumeAvailableNativeProcessOutput(bool blocking) { - if (_isRunningInBackground == false) + 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(); + } + + if (!stdErrRedirected) + { + return; + } + } - ProcessOutputRecord(record); + if (stdOutRedirected || stdErrRedirected) + { + ProcessOutputObject record; + while ((record = DequeueProcessOutput(blocking)) != null) + { + if (this.Command.Context.CurrentPipelineStopping) + { + this.StopProcessing(); + return; } + + ProcessOutputRecord(record); } } } @@ -677,17 +1085,27 @@ internal override void Complete() Exception exceptionToRethrow = null; try { - if (_isRunningInBackground == false) + 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); _nativeProcess.WaitForExit(); +#if !UNIX + if (_isPackageManager) + { + UpdateProcessEnvPath(_originalUserEnvPath, _originalSystemEnvPath); + } +#endif + // Capture screen output if we are transcribing and running stand alone - if (_isTranscribing && (true == s_supportScreenScrape) && _runStandAlone) + if (_isTranscribing && (s_supportScreenScrape == true) && _runStandAlone) { Host.Coordinates endPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition; endPosition.X = this.Command.Context.EngineHostInterface.UI.RawUI.BufferSize.Width - 1; @@ -724,8 +1142,63 @@ 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); +#if UNIX + string hexFormatStr = "0x{0:X2}"; +#else + string hexFormatStr = "0x{0:X8}"; +#endif + + string errorMsg = StringUtil.Format( + CommandBaseStrings.ProgramExitedWithNonZeroCode, + NativeCommandName, + _nativeProcess.ExitCode, + string.Format(CultureInfo.InvariantCulture, hexFormatStr, _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) @@ -744,7 +1217,7 @@ internal override void Complete() finally { // Do some cleanup - CleanUp(); + CleanUp(killBackgroundProcess: false); } // An exception was thrown while attempting to run the program @@ -823,7 +1296,7 @@ public int ParentId get { // Construct parent id only once. - if (int.MinValue == _parentId) + if (_parentId == int.MinValue) { ConstructParentId(); } @@ -930,30 +1403,24 @@ private static void KillChildProcesses(int parentId, ProcessWithParentId[] curre #region checkForConsoleApplication - /// - /// Return true if the passed in process is a console process. - /// - /// - /// - private static bool IsConsoleApplication(string fileName) - { - return !IsWindowsApplication(fileName); - } - /// /// Check if the passed in process is a windows application. /// /// /// - [ArchitectureSensitive] private static bool IsWindowsApplication(string fileName) { - if (!Platform.IsWindowsDesktop) { return false; } +#if UNIX + return false; +#else + if (!Platform.IsWindowsDesktop) + { + 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 @@ -968,6 +1435,7 @@ private static bool IsWindowsApplication(string fileName) // anything else - is a windows program... return true; } +#endif } #endregion checkForConsoleApplication @@ -983,7 +1451,11 @@ internal void StopProcessing() { lock (_sync) { - if (_stopped) return; + if (_stopped) + { + return; + } + _stopped = true; } @@ -992,8 +1464,12 @@ internal void StopProcessing() if (!_runStandAlone) { // Stop input writer - _inputWriter.Stop(); + if (!UpstreamIsNativeCommand) + { + _inputWriter.Stop(); + } + _stdOutByteTransfer?.Dispose(); KillProcess(_nativeProcess); } } @@ -1004,21 +1480,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) { @@ -1034,7 +1523,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) { @@ -1092,56 +1581,83 @@ 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 + { + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. + FileName = this.Path + }; - 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; + + Encoding outputEncoding = GetOutputEncoding(); + 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 = 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 = outputEncoding; } - - startInfo.UseShellExecute = true; } // For minishell value of -outoutFormat parameter depends on value of redirectOutput. @@ -1149,23 +1665,87 @@ 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)) + { + // codeql[cs/microsoft/command-line-injection ] - This is intended PowerShell behavior as NativeParameterBinderController.Arguments is what the native parameter binder generates based on the user input when invoking the command and cannot be injected externally. + startInfo.Arguments = NativeParameterBinderController.Arguments; + } + } + 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; } - private bool IsDownstreamOutDefault(Pipe downstreamPipe) +#nullable enable + /// + /// Gets the encoding to use for a process' output/error pipes. + /// + /// The encoding to use for the process output. + private Encoding GetOutputEncoding() + { + Encoding? applicationOutputEncoding = Context.GetVariableValue( + SpecialVariables.PSApplicationOutputEncodingVarPath) as Encoding; + + return applicationOutputEncoding ?? Console.OutputEncoding; + } +#nullable disable + + /// + /// 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."); @@ -1192,10 +1772,11 @@ private bool IsDownstreamOutDefault(Pipe downstreamPipe) /// /// This method calculates if input and output of the process are redirected. /// + /// /// /// /// - private void CalculateIORedirection(out bool redirectOutput, out bool redirectError, out bool redirectInput) + private void CalculateIORedirection(bool isWindowsApplication, out bool redirectOutput, out bool redirectError, out bool redirectInput) { redirectInput = this.Command.MyInvocation.ExpectingInput; redirectOutput = true; @@ -1219,15 +1800,19 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr // $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, @@ -1237,7 +1822,9 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr // $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; @@ -1246,7 +1833,7 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr // In minishell scenario, if output is redirected // then error should also be redirected. - if (redirectError == false && redirectOutput && _isMiniShell) + if (!redirectError && redirectOutput && _isMiniShell) { redirectError = true; } @@ -1266,7 +1853,7 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr redirectOutput = true; redirectError = true; } - else if (Platform.IsWindowsDesktop && IsConsoleApplication(this.Path)) + else if (Platform.IsWindowsDesktop && !isWindowsApplication) { // On Windows desktops, if the command to run is a console application, // then allocate a console if there isn't one attached already... @@ -1285,6 +1872,9 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr { if (s_supportScreenScrape == null) { +#if UNIX + s_supportScreenScrape = false; +#else try { _startPosition = this.Command.Context.EngineHostInterface.UI.RawUI.CursorPosition; @@ -1296,11 +1886,12 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr { s_supportScreenScrape = false; } +#endif } // if screen scraping isn't supported, we enable redirection so that the output is still transcribed // as redirected output is always transcribed - if (_isTranscribing && (false == s_supportScreenScrape)) + if (_isTranscribing && (s_supportScreenScrape == false)) { redirectOutput = true; redirectError = true; @@ -1311,6 +1902,10 @@ private void CalculateIORedirection(out bool redirectOutput, out bool redirectEr // On Windows, check the extension list and see if we should try to execute this directly. // Otherwise, use the platform library to check executability + [SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Accesses instance members in preprocessor branch.")] private bool IsExecutable(string path) { #if UNIX @@ -1327,7 +1922,7 @@ private bool IsExecutable(string path) } else { - extensionList = pathext.Split(Utils.Separators.Semicolon); + extensionList = pathext.Split(';'); } foreach (string extension in extensionList) @@ -1342,91 +1937,10 @@ 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; + /// /// Returns true if native command being invoked is mini-shell. /// @@ -1462,14 +1976,26 @@ internal class ProcessOutputHandler internal const string XmlCliTag = "#< CLIXML"; private int _refCount; - private BlockingCollection _queue; + private readonly BlockingCollection _queue; private bool _isFirstOutput; private bool _isFirstError; private bool _isXmlCliOutput; private bool _isXmlCliError; - private string _processFileName; + 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; @@ -1478,19 +2004,17 @@ public ProcessOutputHandler(Process process, BlockingCollection /// Creates an instance of ProcessInputWriter. /// @@ -1736,14 +2279,27 @@ internal void Add(object input) return; } - if (_inputFormat == NativeCommandIOFormat.Text) + if (_inputFormat is not NativeCommandIOFormat.Text) { - AddTextInput(input); + AddXmlInput(input); + return; } - else // Xml + + object baseObjInput = PSObject.Base(input); + + if (baseObjInput is byte[] bytes) { - AddXmlInput(input); + _streamWriter.BaseStream.Write(bytes, 0, bytes.Length); + return; + } + + if (baseObjInput is byte b) + { + _streamWriter.BaseStream.WriteByte(b); + return; } + + AddTextInput(input); } private void AddTextInput(object input) @@ -1812,11 +2368,12 @@ internal void Start(Process process, NativeCommandIOFormat inputFormat) // Get the encoding for writing to native command. Note we get the Encoding // from the current scope so a script or function can use a different encoding // than global value. - Encoding pipeEncoding = _command.Context.GetVariableValue(SpecialVariables.OutputEncodingVarPath) as System.Text.Encoding ?? - Utils.utf8NoBom; + Encoding outputEncoding = _command.Context.GetVariableValue(SpecialVariables.OutputEncodingVarPath) as Encoding; - _streamWriter = new StreamWriter(process.StandardInput.BaseStream, pipeEncoding); - _streamWriter.AutoFlush = true; + _streamWriter = new StreamWriter(process.StandardInput.BaseStream, outputEncoding ?? Encoding.Default) + { + AutoFlush = true + }; _inputFormat = inputFormat; @@ -1883,10 +2440,7 @@ internal void Done() { if (_inputFormat == NativeCommandIOFormat.Xml) { - if (_xmlSerializer != null) - { - _xmlSerializer.Done(); - } + _xmlSerializer?.Done(); } else // Text { @@ -1914,58 +2468,6 @@ internal static class ConsoleVisibility /// 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 it 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 @@ -1974,90 +2476,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 { /// @@ -2133,21 +2600,22 @@ 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 [NonSerialized] - private PSObject _serializedRemoteException; + private readonly PSObject _serializedRemoteException; [NonSerialized] - private PSObject _serializedRemoteInvocationInfo; + 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. /// @@ -2163,7 +2631,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 199f7f1b2b7..10b2cf1101d 100644 --- a/src/System.Management.Automation/engine/PSClassInfo.cs +++ b/src/System.Management.Automation/engine/PSClassInfo.cs @@ -23,7 +23,7 @@ internal PSClassInfo(string name) /// /// Name of the class. /// - public string Name { get; private set; } + public string Name { get; } /// /// Collection of members of the class. @@ -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; @@ -72,16 +71,16 @@ internal PSClassMemberInfo(string name, string memberType, string defaultValue) /// /// Gets or sets name of the member. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets or sets type of the member. /// - public string TypeName { get; private set; } + public string TypeName { get; } /// /// Default value of the Field. /// - public string DefaultValue { get; private set; } + public string DefaultValue { get; } } } diff --git a/src/System.Management.Automation/engine/PSClassSearcher.cs b/src/System.Management.Automation/engine/PSClassSearcher.cs index 613035820d0..ce47ae93e16 100644 --- a/src/System.Management.Automation/engine/PSClassSearcher.cs +++ b/src/System.Management.Automation/engine/PSClassSearcher.cs @@ -33,14 +33,14 @@ internal PSClassSearcher( #region private properties - private string _className = null; - private ExecutionContext _context = null; + private readonly string _className = null; + private readonly ExecutionContext _context = null; private PSClassInfo _currentMatch = null; private IEnumerator _matchingClass = null; private Collection _matchingClassList = null; - private bool _useWildCards = false; - private Dictionary _moduleInfoCache = null; - private object _lockObject = new object(); + private readonly bool _useWildCards = false; + private readonly Dictionary _moduleInfoCache = null; + private readonly object _lockObject = new object(); #endregion @@ -288,7 +288,7 @@ private Collection GetPSModuleInfo(string modulePath) return modules; } - private PSClassInfo ConvertToClassInfo(PSModuleInfo module, ScriptBlockAst ast, TypeDefinitionAst statement) + private static PSClassInfo ConvertToClassInfo(PSModuleInfo module, ScriptBlockAst ast, TypeDefinitionAst statement) { PSClassInfo classInfo = new PSClassInfo(statement.Name); Dbg.Assert(statement.Name != null, "statement should have a name."); diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index aacf5c50e32..419a4cae95f 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -89,7 +89,10 @@ private PowerShellConfig() // Note: This directory may or may not exist depending upon the execution scenario. // Writes will attempt to create the directory if it does not already exist. perUserConfigDirectory = Platform.ConfigDirectory; - perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + if (!string.IsNullOrEmpty(perUserConfigDirectory)) + { + perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + } emptyConfig = new JObject(); configRoots = new JObject[2]; @@ -174,13 +177,14 @@ internal void SetExecutionPolicy(ConfigScope scope, string shellId, string execu WriteValueToFile(scope, key, executionPolicy); } - private string GetExecutionPolicySettingKey(string shellId) + private static string GetExecutionPolicySettingKey(string shellId) { return string.Equals(shellId, Utils.DefaultPowerShellShellID, StringComparison.Ordinal) ? ExecutionPolicyDefaultShellKey : string.Concat(shellId, ":", "ExecutionPolicy"); } + /// /// Get the names of experimental features enabled in the config file. /// internal string[] GetExperimentalFeatures() @@ -290,12 +294,12 @@ internal PSLevel GetLogLevel() /// /// The supported separator characters for listing channels and keywords in configuration. /// - static readonly char[] s_valueSeparators = new char[] {' ', ',', '|'}; + private static readonly char[] s_valueSeparators = new char[] {' ', ',', '|'}; /// /// Provides a string name to indicate the default for a configuration setting. /// - const string LogDefaultValue = "default"; + private const string LogDefaultValue = "default"; /// /// Gets the bitmask of the PSChannel values to log. @@ -386,6 +390,11 @@ internal PSKeyword GetLogKeywords() private T ReadValueFromFile(ConfigScope scope, string key, T defaultValue = default) { string fileName = GetConfigFilePath(scope); + if (string.IsNullOrEmpty(fileName)) + { + return defaultValue; + } + JObject configData = configRoots[(int)scope]; if (configData == null) @@ -402,6 +411,10 @@ private T ReadValueFromFile(ConfigScope scope, string key, T defaultValue = d configData = serializer.Deserialize(jsonReader) ?? emptyConfig; } + catch (Exception exc) + { + throw PSTraceSource.NewInvalidOperationException(exc, PSConfigurationStrings.CanNotConfigurationFile, args: fileName); + } finally { fileLock.ExitReadLock(); @@ -562,7 +575,7 @@ private void UpdateValueInFile(ConfigScope scope, string key, T value, bool a /// The value to write. private void WriteValueToFile(ConfigScope scope, string key, T value) { - if (ConfigScope.CurrentUser == scope && !Directory.Exists(perUserConfigDirectory)) + if (scope == ConfigScope.CurrentUser && !Directory.Exists(perUserConfigDirectory)) { Directory.CreateDirectory(perUserConfigDirectory); } @@ -636,11 +649,17 @@ private void RemoveValueFromFile(ConfigScope scope, string key) internal sealed class PowerShellPolicies { public ScriptExecution ScriptExecution { get; set; } + public ScriptBlockLogging ScriptBlockLogging { get; set; } + public ModuleLogging ModuleLogging { get; set; } + public ProtectedEventLogging ProtectedEventLogging { get; set; } + public Transcription Transcription { get; set; } + public UpdatableHelp UpdatableHelp { get; set; } + public ConsoleSessionConfiguration ConsoleSessionConfiguration { get; set; } } @@ -652,6 +671,7 @@ internal abstract class PolicyBase { } internal sealed class ScriptExecution : PolicyBase { public string ExecutionPolicy { get; set; } + public bool? EnableScripts { get; set; } } @@ -661,6 +681,7 @@ internal sealed class ScriptExecution : PolicyBase internal sealed class ScriptBlockLogging : PolicyBase { public bool? EnableScriptBlockInvocationLogging { get; set; } + public bool? EnableScriptBlockLogging { get; set; } } @@ -670,6 +691,7 @@ internal sealed class ScriptBlockLogging : PolicyBase internal sealed class ModuleLogging : PolicyBase { public bool? EnableModuleLogging { get; set; } + public string[] ModuleNames { get; set; } } @@ -679,7 +701,9 @@ internal sealed class ModuleLogging : PolicyBase internal sealed class Transcription : PolicyBase { public bool? EnableTranscripting { get; set; } + public bool? EnableInvocationHeader { get; set; } + public string OutputDirectory { get; set; } } @@ -689,6 +713,7 @@ internal sealed class Transcription : PolicyBase internal sealed class UpdatableHelp : PolicyBase { public bool? EnableUpdateHelpDefaultSourcePath { get; set; } + public string DefaultSourcePath { get; set; } } @@ -698,6 +723,7 @@ internal sealed class UpdatableHelp : PolicyBase internal sealed class ConsoleSessionConfiguration : PolicyBase { public bool? EnableConsoleSessionConfiguration { get; set; } + public string ConsoleSessionConfigurationName { get; set; } } @@ -707,6 +733,7 @@ internal sealed class ConsoleSessionConfiguration : PolicyBase internal sealed class ProtectedEventLogging : PolicyBase { public bool? EnableProtectedEventLogging { get; set; } + public string[] EncryptionCertificate { get; set; } } diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index 09f9829c289..eef9819a568 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Collections; -using System.Diagnostics; using System.Globalization; 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,18 +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_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. @@ -76,43 +85,18 @@ static PSVersionInfo() { s_psVersionTable = new PSVersionHashTable(StringComparer.OrdinalIgnoreCase); - string assemblyPath = typeof(PSVersionInfo).Assembly.Location; - string productVersion = FileVersionInfo.GetVersionInfo(assemblyPath).ProductVersion; - - // 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_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; } @@ -181,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. /// @@ -204,7 +172,7 @@ public static string PSEdition { get { - return (string)s_psVersionTable[PSVersionInfo.PSEditionName]; + return PSEditionValue; } } @@ -216,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"; - } - } - /// /// /// @@ -266,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; } @@ -373,7 +282,7 @@ public override ICollection Keys } } - private class PSVersionTableComparer : IComparer + private sealed class PSVersionTableComparer : IComparer { public int Compare(object x, object y) { @@ -426,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"; @@ -461,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; } @@ -495,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; @@ -519,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; @@ -563,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; @@ -632,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; } @@ -651,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); @@ -700,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('+'); @@ -725,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; @@ -733,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' @@ -779,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; @@ -798,16 +749,16 @@ 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)) { - result.Append("-").Append(PreReleaseLabel); + result.Append('-').Append(PreReleaseLabel); } if (!string.IsNullOrEmpty(BuildLabel)) { - result.Append("+").Append(BuildLabel); + result.Append('+').Append(BuildLabel); } versionString = result.ToString(); @@ -844,8 +795,7 @@ public int CompareTo(object version) return 1; } - var v = version as SemanticVersion; - if (v == null) + if (version is not SemanticVersion v) { throw PSTraceSource.NewArgumentException(nameof(version)); } @@ -855,11 +805,11 @@ 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) { - if ((object)value == null) + if (value is null) return 1; if (Major != value.Major) @@ -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 4dcb5488041..21868b1a28d 100644 --- a/src/System.Management.Automation/engine/ParameterBinderBase.cs +++ b/src/System.Management.Automation/engine/ParameterBinderBase.cs @@ -58,7 +58,7 @@ internal abstract class ParameterBinderBase { #region tracer [TraceSource("ParameterBinderBase", "A abstract helper class for the CommandProcessor that binds parameters to the specified object.")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("ParameterBinderBase", "A abstract helper class for the CommandProcessor that binds parameters to the specified object."); + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ParameterBinderBase", "A abstract helper class for the CommandProcessor that binds parameters to the specified object."); [TraceSource("ParameterBinding", "Traces the process of binding the arguments to the parameters of cmdlets, scripts, and applications.")] internal static readonly PSTraceSource bindingTracer = @@ -172,10 +172,10 @@ internal object Target /// internal CommandLineParameters CommandLineParameters { + get { return _commandLineParameters ??= new CommandLineParameters(); } + // Setter is needed to pass into RuntimeParameterBinder instances set { _commandLineParameters = value; } - - get { return _commandLineParameters ?? (_commandLineParameters = new CommandLineParameters()); } } private CommandLineParameters _commandLineParameters; @@ -439,7 +439,7 @@ internal virtual bool BindParameter( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), ParameterBinderStrings.ParameterArgumentTransformationError, "ParameterArgumentTransformationError", e.Message); @@ -522,7 +522,7 @@ internal virtual bool BindParameter( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), ParameterBinderStrings.ParameterArgumentValidationError, "ParameterArgumentValidationError", e.Message); @@ -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 @@ -586,7 +584,7 @@ internal virtual bool BindParameter( if (bindError != null) { - Type specifiedType = (parameterValue == null) ? null : parameterValue.GetType(); + Type specifiedType = parameterValue?.GetType(); ParameterBindingException bindingException = new ParameterBindingException( bindError, @@ -736,7 +734,7 @@ private void ValidateNullOrEmptyArgument( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), ParameterBinderStrings.ParameterArgumentValidationErrorEmptyStringNotAllowed, "ParameterArgumentValidationErrorEmptyStringNotAllowed"); throw bindingException; @@ -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. @@ -813,7 +814,7 @@ private void ValidateNullOrEmptyArgument( GetErrorExtent(parameter), parameterMetadata.Name, parameterMetadata.Type, - (parameterValue == null) ? null : parameterValue.GetType(), + parameterValue?.GetType(), resourceString, errorId); throw bindingException; @@ -903,7 +904,7 @@ private bool ShouldContinueUncoercedBind( /// /// The invocation information for the code that is being bound. /// - private InvocationInfo _invocationInfo; + private readonly InvocationInfo _invocationInfo; internal InvocationInfo InvocationInfo { @@ -916,7 +917,7 @@ internal InvocationInfo InvocationInfo /// /// The context of the currently running engine. /// - private ExecutionContext _context; + private readonly ExecutionContext _context; internal ExecutionContext Context { @@ -929,7 +930,7 @@ internal ExecutionContext Context /// /// An instance of InternalCommand that the binder is binding to. /// - private InternalCommand _command; + private readonly InternalCommand _command; internal InternalCommand Command { @@ -942,9 +943,9 @@ internal InternalCommand Command /// /// The engine APIs that need to be passed the attributes when evaluated. /// - private EngineIntrinsics _engine; + private readonly EngineIntrinsics _engine; - private bool _isTranscribing; + private readonly bool _isTranscribing; #endregion internal members @@ -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, @@ -1559,7 +1558,7 @@ private object EncodeCollection( toType, 0, null, - new object[] { }, + Array.Empty(), System.Globalization.CultureInfo.InvariantCulture); if (collectionTypeInformation.ParameterCollectionType == ParameterCollectionType.IList) resultAsIList = (IList)resultCollection; @@ -1781,7 +1780,7 @@ private object EncodeCollection( GetErrorExtent(argument), parameterName, toType, - (currentValueElement == null) ? null : currentValueElement.GetType(), + currentValueElement?.GetType(), ParameterBinderStrings.CannotConvertArgument, "CannotConvertArgument", currentValueElement ?? "null", @@ -1878,7 +1877,7 @@ private object EncodeCollection( GetErrorExtent(argument), parameterName, toType, - (currentValue == null) ? null : currentValue.GetType(), + currentValue?.GetType(), ParameterBinderStrings.CannotConvertArgument, "CannotConvertArgument", currentValue ?? "null", @@ -1985,7 +1984,7 @@ internal PSBoundParametersDictionary() private static readonly IDictionary s_emptyUsingParameters = new ReadOnlyDictionary(new Dictionary()); - public List BoundPositionally { get; private set; } + public List BoundPositionally { get; } internal IDictionary ImplicitUsingParameters { get; set; } } @@ -2066,4 +2065,3 @@ internal HashSet CopyBoundPositionalParameters() } } } - diff --git a/src/System.Management.Automation/engine/ParameterBinderController.cs b/src/System.Management.Automation/engine/ParameterBinderController.cs index a68192c7a90..f9781a99435 100644 --- a/src/System.Management.Automation/engine/ParameterBinderController.cs +++ b/src/System.Management.Automation/engine/ParameterBinderController.cs @@ -56,7 +56,7 @@ internal ParameterBinderController(InvocationInfo invocationInfo, ExecutionConte /// /// Gets the parameter binder for the command. /// - internal ParameterBinderBase DefaultParameterBinder { get; private set; } + internal ParameterBinderBase DefaultParameterBinder { get; } /// /// The invocation information about the code being run. @@ -130,7 +130,7 @@ internal void ClearUnboundArguments() /// Or /// The name of the argument matches more than one parameter. /// - internal void ReparseUnboundArguments() + protected void ReparseUnboundArguments() { Collection result = new Collection(); @@ -260,6 +260,34 @@ internal void ReparseUnboundArguments() UnboundArguments = result; } + protected void InitUnboundArguments(Collection arguments) + { + // Add the passed in arguments to the unboundArguments collection + Collection paramsFromSplatting = null; + foreach (CommandParameterInternal argument in arguments) + { + if (argument.FromHashtableSplatting) + { + paramsFromSplatting ??= new Collection(); + paramsFromSplatting.Add(argument); + } + else + { + UnboundArguments.Add(argument); + } + } + + // Move the arguments from hashtable splatting to the end of the unbound args list, so that + // the explicitly specified named arguments can supersede those from a hashtable splatting. + if (paramsFromSplatting != null) + { + foreach (CommandParameterInternal argument in paramsFromSplatting) + { + UnboundArguments.Add(argument); + } + } + } + private static bool IsSwitchAndSetValue( string argumentName, CommandParameterInternal argument, @@ -431,7 +459,7 @@ internal virtual bool BindParameter( throw bindingException; } - flags = flags & ~ParameterBindingFlags.DelayBindScriptBlock; + flags &= ~ParameterBindingFlags.DelayBindScriptBlock; result = BindParameter(_currentParameterSetFlag, argument, matchingParameter, flags); } @@ -447,7 +475,10 @@ internal virtual bool BindParameter( /// /// The arguments which are still not bound. /// - internal abstract Collection BindParameters(Collection parameters); + internal virtual Collection BindParameters(Collection parameters) + { + throw new NotImplementedException(); + } /// /// Bind the argument to the specified parameter. @@ -514,6 +545,121 @@ internal virtual bool BindParameter( return result; } + /// + /// This is used by to validate and bind a given named parameter. + /// + protected virtual void BindNamedParameter( + uint parameterSets, + CommandParameterInternal argument, + MergedCompiledCommandParameter parameter) + { + BindParameter(parameterSets, argument, parameter, ParameterBindingFlags.ShouldCoerceType); + } + + /// + /// Bind the named parameters from the specified argument collection, + /// for only the parameters in the specified parameter set. + /// + /// + /// The parameter set used to bind the arguments. + /// + /// + /// The arguments that should be attempted to bind to the parameters of the specified parameter binder. + /// + /// + /// if multiple parameters are found matching the name. + /// or + /// if no match could be found. + /// or + /// If argument transformation fails. + /// or + /// The argument could not be coerced to the appropriate type for the parameter. + /// or + /// The parameter argument transformation, prerequisite, or validation failed. + /// or + /// If the binding to the parameter fails. + /// + protected Collection BindNamedParameters(uint parameterSets, Collection arguments) + { + Collection result = new Collection(); + HashSet boundExplicitNamedParams = null; + + foreach (CommandParameterInternal argument in arguments) + { + if (!argument.ParameterNameSpecified) + { + result.Add(argument); + continue; + } + + // We don't want to throw an exception yet because the parameter might be a positional argument, + // or in case of a cmdlet or an advanced function, it might match up to a dynamic parameter. + MergedCompiledCommandParameter parameter = + BindableParameters.GetMatchingParameter( + name: argument.ParameterName, + throwOnParameterNotFound: false, + tryExactMatching: true, + invocationInfo: new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent)); + + // If the parameter is not in the specified parameter set, throw a binding exception + if (parameter != null) + { + string formalParamName = parameter.Parameter.Name; + + if (argument.FromHashtableSplatting) + { + boundExplicitNamedParams ??= new HashSet( + BoundParameters.Keys, + StringComparer.OrdinalIgnoreCase); + + if (boundExplicitNamedParams.Contains(formalParamName)) + { + // 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 superseded by the explicit one. For example: + // $splat = @{ Path = $path1 } + // dir @splat -Path $path2 + continue; + } + } + + // Now check to make sure it hasn't already been + // bound by looking in the boundParameters collection + + if (BoundParameters.ContainsKey(formalParamName)) + { + ParameterBindingException bindingException = + new ParameterBindingException( + ErrorCategory.InvalidArgument, + this.InvocationInfo, + GetParameterErrorExtent(argument), + argument.ParameterName, + null, + null, + ParameterBinderStrings.ParameterAlreadyBound, + nameof(ParameterBinderStrings.ParameterAlreadyBound)); + + throw bindingException; + } + + BindNamedParameter(parameterSets, argument, parameter); + } + else if (argument.ParameterName.Equals(Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal)) + { + // We sometimes send a magic parameter from a remote machine with the values referenced via + // a using expression ($using:x). We then access these values via PSBoundParameters, so + // "bind" them here. + DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue); + } + else + { + result.Add(argument); + } + } + + return result; + } + /// /// Binds the unbound arguments to positional parameters. /// @@ -883,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; @@ -1178,4 +1324,3 @@ protected IScriptExtent GetParameterErrorExtent(CommandParameterInternal cpi) #endregion internal_members } } - diff --git a/src/System.Management.Automation/engine/ParameterInfo.cs b/src/System.Management.Automation/engine/ParameterInfo.cs index cf1784a7b62..d1def1c468f 100644 --- a/src/System.Management.Automation/engine/ParameterInfo.cs +++ b/src/System.Management.Automation/engine/ParameterInfo.cs @@ -144,4 +144,3 @@ private void SetParameterSetData(ParameterSetSpecificMetadata parameterMetadata) #endregion private members } } - diff --git a/src/System.Management.Automation/engine/ParameterSetInfo.cs b/src/System.Management.Automation/engine/ParameterSetInfo.cs index c166f749b1c..5d81b8553cf 100644 --- a/src/System.Management.Automation/engine/ParameterSetInfo.cs +++ b/src/System.Management.Automation/engine/ParameterSetInfo.cs @@ -72,12 +72,12 @@ internal CommandParameterSetInfo( /// /// Gets the name of the parameter set. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets whether the parameter set is the default parameter set. /// - public bool IsDefault { get; private set; } + public bool IsDefault { get; } /// /// Gets the parameter information for the parameters in this parameter set. @@ -92,18 +92,18 @@ public override string ToString() Text.StringBuilder result = new Text.StringBuilder(); GenerateParametersInDisplayOrder( - parameter => AppendFormatCommandParameterInfo(parameter, result), - delegate (string str) - { - if (result.Length > 0) - { - result.Append(" "); - } - - result.Append("["); - result.Append(str); - result.Append("]"); - }); + parameter => AppendFormatCommandParameterInfo(parameter, result), + (string str) => + { + if (result.Length > 0) + { + result.Append(' '); + } + + result.Append('['); + result.Append(str); + result.Append(']'); + }); return result.ToString(); } @@ -233,7 +233,7 @@ private static void AppendFormatCommandParameterInfo(CommandParameterInfo parame if (result.Length > 0) { // Add a space between parameters - result.Append(" "); + result.Append(' '); } if (parameter.ParameterType == typeof(SwitchParameter)) @@ -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); @@ -339,4 +343,3 @@ private void Initialize(MergedCommandParameterMetadata parameterMetadata, uint p #endregion private members } } - diff --git a/src/System.Management.Automation/engine/ParameterSetPromptingData.cs b/src/System.Management.Automation/engine/ParameterSetPromptingData.cs index 0f7889f8d0b..36e735e675a 100644 --- a/src/System.Management.Automation/engine/ParameterSetPromptingData.cs +++ b/src/System.Management.Automation/engine/ParameterSetPromptingData.cs @@ -60,4 +60,3 @@ internal Dictionary(); } } - diff --git a/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs b/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs index 4cc4ca7540e..a20b2af2061 100644 --- a/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs +++ b/src/System.Management.Automation/engine/ParameterSetSpecificMetadata.cs @@ -209,7 +209,6 @@ internal string GetHelpMessage(Cmdlet cmdlet) return helpInfo; } - private ParameterAttribute _attribute; + private readonly ParameterAttribute _attribute; } } - diff --git a/src/System.Management.Automation/engine/PathInterfaces.cs b/src/System.Management.Automation/engine/PathInterfaces.cs index d11a225797e..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. @@ -1402,14 +1402,13 @@ private LocationGlobber PathResolver _sessionState != null, "The only constructor for this class should always set the sessionState field"); - return _pathResolver ?? (_pathResolver = _sessionState.ExecutionContext.LocationGlobber); + return _pathResolver ??= _sessionState.ExecutionContext.LocationGlobber; } } private LocationGlobber _pathResolver; - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/Pipe.cs b/src/System.Management.Automation/engine/Pipe.cs index ac9350487a0..d43a0f96a5d 100644 --- a/src/System.Management.Automation/engine/Pipe.cs +++ b/src/System.Management.Automation/engine/Pipe.cs @@ -16,7 +16,7 @@ internal enum VariableStreamKind Error, Warning, Information - }; + } /// /// Pipe provides a way to stitch two commands. @@ -28,7 +28,7 @@ internal enum VariableStreamKind /// internal class Pipe { - private ExecutionContext _context; + private readonly ExecutionContext _context; // If a pipeline object has been added, then // write objects to it, stepping one at a time... @@ -40,7 +40,10 @@ internal class Pipe /// internal CommandProcessorBase DownstreamCmdlet { - get { return _downstreamCmdlet; } + get + { + return _downstreamCmdlet; + } set { @@ -76,7 +79,10 @@ internal CommandProcessorBase DownstreamCmdlet /// internal PipelineWriter ExternalWriter { - get { return _externalWriter; } + get + { + return _externalWriter; + } set { @@ -103,12 +109,22 @@ 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... /// internal bool NullPipe { - get { return _nullPipe; } + get + { + return _nullPipe; + } set { @@ -217,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; @@ -299,7 +303,7 @@ internal void SetVariableListForTemporaryPipe(Pipe tempPipe) CopyVariableToTempPipe(VariableStreamKind.Information, _informationVariableList, tempPipe); } - private void CopyVariableToTempPipe(VariableStreamKind streamKind, List variableList, Pipe tempPipe) + private static void CopyVariableToTempPipe(VariableStreamKind streamKind, List variableList, Pipe tempPipe) { if (variableList != null && variableList.Count > 0) { @@ -349,7 +353,7 @@ internal Pipe(System.Collections.ObjectModel.Collection resultCollecti _resultCollection = resultCollection; } - private System.Collections.ObjectModel.Collection _resultCollection; + private readonly System.Collections.ObjectModel.Collection _resultCollection; /// /// This pipe writes into another pipeline processor allowing @@ -380,7 +384,7 @@ internal Pipe(IEnumerator enumeratorToProcess) _enumeratorToProcessIsEmpty = false; } - private IEnumerator _enumeratorToProcess; + private readonly IEnumerator _enumeratorToProcess; private bool _enumeratorToProcessIsEmpty; #endregion ctor @@ -510,7 +514,7 @@ internal void AddItems(object objects) // If our object came from GetEnumerator (and hence is not IEnumerator), then we need to dispose // Otherwise, we don't own the object, so don't dispose. var disposable = ie as IDisposable; - if (disposable != null && !(objects is IEnumerator)) + if (disposable != null && objects is not IEnumerator) { disposable.Dispose(); } @@ -543,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) { @@ -586,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/PositionalCommandParameter.cs b/src/System.Management.Automation/engine/PositionalCommandParameter.cs index 1370d8c9a2f..3a2b4688ab4 100644 --- a/src/System.Management.Automation/engine/PositionalCommandParameter.cs +++ b/src/System.Management.Automation/engine/PositionalCommandParameter.cs @@ -25,4 +25,3 @@ internal PositionalCommandParameter(MergedCompiledCommandParameter parameter) internal Collection ParameterSetData { get; } = new Collection(); } } - 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 f040e24e416..b3139dc8e6e 100644 --- a/src/System.Management.Automation/engine/ProgressRecord.cs +++ b/src/System.Management.Automation/engine/ProgressRecord.cs @@ -15,8 +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 { @@ -35,7 +34,6 @@ class ProgressRecord /// /// A description of the status of the activity. /// - public ProgressRecord(int activityId, string activity, string statusDescription) { @@ -61,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) /// @@ -81,7 +98,6 @@ internal ProgressRecord(ProgressRecord other) /// Gets the Id of the activity to which this record corresponds. Used as a 'key' for the /// linking of subordinate activities. /// - public int ActivityId @@ -107,7 +123,6 @@ internal ProgressRecord(ProgressRecord other) /// shell so that a script can set that variable, and have all subsequent calls to WriteProgress (the API) be /// subordinate to the "current parent id".--> /// - public int ParentActivityId @@ -135,7 +150,6 @@ internal ProgressRecord(ProgressRecord other) /// States the overall intent of whats being accomplished, such as "Recursively removing item c:\temp." Typically /// displayed in conjunction with a progress bar. /// - public string Activity @@ -159,7 +173,6 @@ internal ProgressRecord(ProgressRecord other) /// /// Gets and sets the current status of the operation, e.g., "35 of 50 items Copied." or "95% completed." or "100 files purged." /// - public string StatusDescription @@ -185,7 +198,6 @@ internal ProgressRecord(ProgressRecord other) /// below its associated progress bar, e.g., "deleting file foo.bar" /// Set to null or empty in the case a sub-activity will be used to show the current operation. /// - public string CurrentOperation @@ -207,7 +219,6 @@ internal ProgressRecord(ProgressRecord other) /// Gets and sets the estimate of the percentage of total work for the activity that is completed. Typically displayed as a progress bar. /// Set to a negative value to indicate that the percentage completed should not be displayed. /// - public int PercentComplete @@ -238,10 +249,9 @@ 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 int SecondsRemaining @@ -262,7 +272,6 @@ internal ProgressRecord(ProgressRecord other) /// /// Gets and sets the type of record represented by this instance. /// - public ProgressRecordType RecordType @@ -284,14 +293,13 @@ internal ProgressRecord(ProgressRecord other) } /// - /// Overrides + /// Overrides /// /// /// "parent = a id = b act = c stat = d cur = e pct = f sec = g type = h" where /// a, b, c, d, e, f, and g are the values of ParentActivityId, ActivityId, Activity, StatusDescription, /// CurrentOperation, PercentComplete, SecondsRemaining and RecordType properties. /// - public override string ToString() @@ -371,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 @@ -428,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()] - private int id; + [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 @@ -499,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)); @@ -520,14 +525,14 @@ internal PSObject ToPSObjectForRemoting() /// /// Defines two types of progress record that refer to the beginning (or middle) and end of an operation. /// - 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. @@ -538,20 +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/PropertyCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/PropertyCmdletProviderInterfaces.cs index 9c1f9379190..4b6e8d818f1 100644 --- a/src/System.Management.Automation/engine/PropertyCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/PropertyCmdletProviderInterfaces.cs @@ -1824,10 +1824,9 @@ internal object MovePropertyDynamicParameters( #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/ProviderInterfaces.cs b/src/System.Management.Automation/engine/ProviderInterfaces.cs index 27e2680af5c..e9edcb48415 100644 --- a/src/System.Management.Automation/engine/ProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/ProviderInterfaces.cs @@ -160,9 +160,8 @@ internal int Count #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/ProviderNames.cs b/src/System.Management.Automation/engine/ProviderNames.cs index 0ae3e63edec..6d9e3d2d598 100644 --- a/src/System.Management.Automation/engine/ProviderNames.cs +++ b/src/System.Management.Automation/engine/ProviderNames.cs @@ -130,4 +130,3 @@ internal override string Registry } } } - diff --git a/src/System.Management.Automation/engine/ProxyCommand.cs b/src/System.Management.Automation/engine/ProxyCommand.cs index 1bb3d15baf3..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; @@ -278,11 +302,11 @@ private static void AppendContent(StringBuilder sb, string section, object obj) string text = GetObjText(obj); if (!string.IsNullOrEmpty(text)) { - sb.Append("\n"); + sb.Append('\n'); sb.Append(section); sb.Append("\n\n"); sb.Append(text); - sb.Append("\n"); + sb.Append('\n'); } } } @@ -306,13 +330,13 @@ private static void AppendContent(StringBuilder sb, string section, PSObject[] a } sb.Append(text); - sb.Append("\n"); + sb.Append('\n'); } } if (!first) { - sb.Append("\n"); + sb.Append('\n'); } } } @@ -327,7 +351,7 @@ private static void AppendType(StringBuilder sb, string section, PSObject parent sb.Append(section); sb.Append("\n\n"); sb.Append(GetObjText(name)); - sb.Append("\n"); + sb.Append('\n'); } else { @@ -338,7 +362,7 @@ private static void AppendType(StringBuilder sb, string section, PSObject parent sb.Append(section); sb.Append("\n\n"); sb.Append(GetObjText(uri)); - sb.Append("\n"); + sb.Append('\n'); } } } @@ -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) @@ -395,7 +416,7 @@ public static string GetHelpComments(PSObject help) if (!string.IsNullOrEmpty(text)) { sb.Append(text); - sb.Append("\n"); + sb.Append('\n'); } } } @@ -430,7 +451,7 @@ public static string GetHelpComments(PSObject help) PSObject[] remarks = GetProperty(ex, "remarks"); if (remarks != null) { - exsb.Append("\n"); + exsb.Append('\n'); foreach (PSObject remark in remarks) { string remarkText = GetProperty(remark, "text"); @@ -441,7 +462,7 @@ public static string GetHelpComments(PSObject help) if (exsb.Length > 0) { sb.Append("\n\n.EXAMPLE\n\n"); - sb.Append(exsb.ToString()); + sb.Append(exsb); } } } diff --git a/src/System.Management.Automation/engine/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/PseudoParameterBinder.cs index bddbe643f59..9bebe01b2e7 100644 --- a/src/System.Management.Automation/engine/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/PseudoParameterBinder.cs @@ -36,7 +36,7 @@ internal RuntimeDefinedParameterBinder( { string key = pair.Key; RuntimeDefinedParameter pp = pair.Value; - string ppName = (pp == null) ? null : pp.Name; + string ppName = pp?.Name; if (pp == null || key != ppName) { ParameterBindingException bindingException = diff --git a/src/System.Management.Automation/engine/PseudoParameters.cs b/src/System.Management.Automation/engine/PseudoParameters.cs index 78ef034cf5b..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 { /// @@ -236,6 +241,6 @@ public string HelpFile /// public object Data { get; set; } - internal static readonly RuntimeDefinedParameter[] EmptyParameterArray = new RuntimeDefinedParameter[0]; + internal static readonly RuntimeDefinedParameter[] EmptyParameterArray = Array.Empty(); } } diff --git a/src/System.Management.Automation/engine/QuestionMarkVariable.cs b/src/System.Management.Automation/engine/QuestionMarkVariable.cs index af2436c2f2a..b25cb5107ba 100644 --- a/src/System.Management.Automation/engine/QuestionMarkVariable.cs +++ b/src/System.Management.Automation/engine/QuestionMarkVariable.cs @@ -41,4 +41,3 @@ public override object Value } } } - diff --git a/src/System.Management.Automation/engine/ReflectionParameterBinder.cs b/src/System.Management.Automation/engine/ReflectionParameterBinder.cs index 3e5dcab2502..5ee84f4c42f 100644 --- a/src/System.Management.Automation/engine/ReflectionParameterBinder.cs +++ b/src/System.Management.Automation/engine/ReflectionParameterBinder.cs @@ -124,7 +124,7 @@ internal override void BindParameter(string name, object value, CompiledCommandP try { var setter = parameterMetadata != null - ? (parameterMetadata.Setter ?? (parameterMetadata.Setter = GetSetter(Target.GetType(), name))) + ? (parameterMetadata.Setter ??= GetSetter(Target.GetType(), name)) : GetSetter(Target.GetType(), name); setter(Target, value); } @@ -155,63 +155,72 @@ 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) => { + (o, v) => + { v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); ((CommonParameters)o).ErrorAction = (ActionPreference)v; }); s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "WarningAction"), - (o, v) => { + (o, v) => + { v ??= LanguagePrimitives.ThrowInvalidCastException(null, typeof(ActionPreference)); ((CommonParameters)o).WarningAction = (ActionPreference)v; }); s_setterMethods.TryAdd(Tuple.Create(typeof(CommonParameters), "InformationAction"), - (o, v) => { + (o, v) => + { 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/ScopedItemSearcher.cs b/src/System.Management.Automation/engine/ScopedItemSearcher.cs index 4d5f2f1107f..fc67ee26374 100644 --- a/src/System.Management.Automation/engine/ScopedItemSearcher.cs +++ b/src/System.Management.Automation/engine/ScopedItemSearcher.cs @@ -111,7 +111,6 @@ public bool MoveNext() /// /// Gets the current scoped item. /// - T IEnumerator.Current { get @@ -231,7 +230,7 @@ private void InitializeScopeEnumerator() private T _current; protected SessionStateInternal sessionState; - private VariablePath _lookupPath; + private readonly VariablePath _lookupPath; private SessionStateScopeEnumerator _scopeEnumerable; private bool _isSingleScopeLookup; private bool _isInitialized; @@ -275,7 +274,7 @@ protected override bool GetScopeItem( VariablePath name, out PSVariable variable) { - Diagnostics.Assert(!(name is FunctionLookupPath), + Diagnostics.Assert(name is not FunctionLookupPath, "name was scanned incorrect if we get here and it is a FunctionLookupPath"); bool result = true; @@ -329,7 +328,7 @@ protected override bool GetScopeItem( VariablePath name, out AliasInfo alias) { - Diagnostics.Assert(!(name is FunctionLookupPath), + Diagnostics.Assert(name is not FunctionLookupPath, "name was scanned incorrect if we get here and it is a FunctionLookupPath"); bool result = true; @@ -471,7 +470,7 @@ protected override bool GetScopeItem( VariablePath name, out PSDriveInfo drive) { - Diagnostics.Assert(!(name is FunctionLookupPath), + Diagnostics.Assert(name is not FunctionLookupPath, "name was scanned incorrect if we get here and it is a FunctionLookupPath"); bool result = true; diff --git a/src/System.Management.Automation/engine/ScriptCommandProcessor.cs b/src/System.Management.Automation/engine/ScriptCommandProcessor.cs index 89e898a5532..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); } } @@ -131,7 +132,7 @@ protected void CommonInitialization(ScriptBlock scriptBlock, ExecutionContext co /// /// Help target to request. /// Help category to request. - /// true if user requested help; false otherwise. + /// if user requested help; otherwise. internal override bool IsHelpRequested(out string helpTarget, out HelpCategory helpCategory) { if (arguments != null && CommandInfo != null && !string.IsNullOrEmpty(CommandInfo.Name) && _scriptBlock != null) @@ -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) @@ -274,7 +275,7 @@ private void Init() { _scriptBlock = base._scriptBlock; _obsoleteAttribute = _scriptBlock.ObsoleteAttribute; - _runOptimizedCode = _scriptBlock.Compile(optimized: _context._debuggingMode > 0 ? false : UseLocalScope); + _runOptimizedCode = _scriptBlock.Compile(optimized: _context._debuggingMode <= 0 && UseLocalScope); _localsTuple = _scriptBlock.MakeLocalsTuple(_runOptimizedCode); if (UseLocalScope) @@ -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 7a5112a341d..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 { @@ -68,7 +68,7 @@ internal override HelpCategory HelpCategory /// /// Gets the ScriptBlock that represents the implementation of the script. /// - public ScriptBlock ScriptBlock { get; private set; } + public ScriptBlock ScriptBlock { get; } // Path @@ -116,9 +116,8 @@ internal override CommandMetadata CommandMetadata { get { - return _commandMetadata ?? - (_commandMetadata = - new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS())); + return _commandMetadata ??= + new CommandMetadata(this.ScriptBlock, this.Name, LocalPipeline.GetExecutionContextFromTLS()); } } diff --git a/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs b/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs index 826fff642c3..0e034289254 100644 --- a/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs +++ b/src/System.Management.Automation/engine/SecurityDescriptorCmdletProviderInterfaces.cs @@ -239,10 +239,9 @@ public ObjectSecurity NewOfType(string providerId, string type, AccessControlSec #region private data - private Cmdlet _cmdlet; - private SessionStateInternal _sessionState; + private readonly Cmdlet _cmdlet; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/SecurityManagerBase.cs b/src/System.Management.Automation/engine/SecurityManagerBase.cs index 3cb4c9e7a70..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 @@ -55,7 +55,7 @@ public AuthorizationManager(string shellId) #endregion constructor - private object _policyCheckLock = new object(); + private readonly object _policyCheckLock = new object(); #region methods to use internally @@ -181,4 +181,3 @@ protected internal virtual bool ShouldRun(CommandInfo commandInfo, #endregion methods for derived class to override } } - diff --git a/src/System.Management.Automation/engine/SerializationStrings.cs b/src/System.Management.Automation/engine/SerializationStrings.cs index c3a1c8a46dc..81b0f9cb741 100644 --- a/src/System.Management.Automation/engine/SerializationStrings.cs +++ b/src/System.Management.Automation/engine/SerializationStrings.cs @@ -308,6 +308,5 @@ internal static class SerializationStrings internal const string MonadNamespacePrefix = "ps"; #endregion namespace values - }; + } } - diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs index f011a87a2aa..a53c211beb2 100644 --- a/src/System.Management.Automation/engine/SessionState.cs +++ b/src/System.Management.Automation/engine/SessionState.cs @@ -18,7 +18,6 @@ namespace System.Management.Automation /// /// Holds the state of a Monad Shell session. /// - [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is a bridge class between internal classes and a public interface. It requires this much coupling.")] internal sealed partial class SessionStateInternal { @@ -28,10 +27,10 @@ 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 Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("SessionState", "SessionState Class"); @@ -144,7 +143,7 @@ internal void InitializeSessionStateInternalSpecialVariables(bool clearVariables /// internal LocationGlobber Globber { - get { return _globberPrivate ?? (_globberPrivate = ExecutionContext.LocationGlobber); } + get { return _globberPrivate ??= ExecutionContext.LocationGlobber; } } private LocationGlobber _globberPrivate; @@ -159,7 +158,7 @@ internal LocationGlobber Globber /// internal SessionState PublicSessionState { - get { return _publicSessionState ?? (_publicSessionState = new SessionState(this)); } + get { return _publicSessionState ??= new SessionState(this); } set { _publicSessionState = value; } } @@ -171,7 +170,7 @@ internal SessionState PublicSessionState /// internal ProviderIntrinsics InvokeProvider { - get { return _invokeProvider ?? (_invokeProvider = new ProviderIntrinsics(this)); } + get { return _invokeProvider ??= new ProviderIntrinsics(this); } } private ProviderIntrinsics _invokeProvider; @@ -338,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); @@ -390,16 +388,29 @@ internal SessionStateEntryVisibility CheckApplicationVisibility(string applicati return checkPathVisibility(Applications, applicationPath); } - private SessionStateEntryVisibility checkPathVisibility(List list, string path) + 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/SessionStateAliasAPIs.cs b/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs index 17ed891f862..fa2cf65e170 100644 --- a/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateAliasAPIs.cs @@ -583,4 +583,3 @@ internal IEnumerable GetAliasesByCommandName(string command) #endregion aliases } } - diff --git a/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs b/src/System.Management.Automation/engine/SessionStateCmdletAPIs.cs index 9e50b4e0a6a..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. @@ -324,4 +324,3 @@ internal void RemoveCmdletEntry(string name, bool force) #endregion cmdlets } } - diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs index f4f2a46d2a5..fa8884b92e8 100644 --- a/src/System.Management.Automation/engine/SessionStateContainer.cs +++ b/src/System.Management.Automation/engine/SessionStateContainer.cs @@ -628,7 +628,7 @@ internal bool IsItemContainer( foreach (string providerPath in providerPaths) { result = IsItemContainer(providerInstance, providerPath, context); - if (result == false) + if (!result) { break; } @@ -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 { @@ -1841,9 +1839,7 @@ private void ProcessPathItems( return; } - string childName = childNameObjects[index].BaseObject as string; - - if (childName == null) + if (childNameObjects[index].BaseObject is not string childName) { continue; } @@ -2063,7 +2059,7 @@ internal object GetChildItemsDynamicParameters( } // Detect if the GetChildItemDynamicParameters has been overridden. - private bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) + private static bool HasGetChildItemDynamicParameters(ProviderInfo providerInfo) { Type providerType = providerInfo.ImplementingType; @@ -2588,9 +2584,7 @@ private void DoGetChildNamesManually( return; } - string name = result.BaseObject as string; - - if (name == null) + if (result.BaseObject is not string name) { continue; } @@ -2638,9 +2632,7 @@ private void DoGetChildNamesManually( return; } - string name = result.BaseObject as string; - - if (name == null) + if (result.BaseObject is not string name) { continue; } @@ -3505,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); @@ -4044,7 +4006,7 @@ private bool HasChildItems( #region CopyItem /// - /// Copies an item at the specified path to an item at the . + /// Copies an item at the specified path to an item at the . /// /// /// The path(s) of the item(s) to copy. @@ -4095,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; @@ -4112,7 +4071,7 @@ internal Collection CopyItem(string[] paths, } /// - /// Copies an item at the specified path to an item at the . + /// Copies an item at the specified path to an item at the . /// /// /// The path(s) of the item(s) to copy. @@ -4161,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; @@ -4219,7 +4174,7 @@ internal void CopyItem( copyPath, context, out destinationProvider, - out unusedDrive); + out _); } else { @@ -4687,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); @@ -4739,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"); @@ -4782,7 +4733,7 @@ private object CopyItemDynamicParameters( // This function validates a remote path, and if it exists, it returns the root path. // - private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession session, CmdletProviderContext context, PSLanguageMode? languageMode, bool sourceIsRemote) + private static string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession session, CmdletProviderContext context, PSLanguageMode? languageMode, bool sourceIsRemote) { Hashtable op = null; @@ -4796,9 +4747,8 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses if (languageMode.HasValue && (languageMode.Value == PSLanguageMode.ConstrainedLanguage || languageMode.Value == PSLanguageMode.NoLanguage)) { - var psRemoteUtilsName = CopyFileRemoteUtils.PSCopyRemoteUtilsName; ps.Runspace = session.Runspace; - ps.AddCommand("Get-Command").AddArgument(psRemoteUtilsName); + ps.AddCommand("Get-Command").AddArgument(CopyFileRemoteUtils.PSCopyRemoteUtilsName); var result = ps.Invoke(); if (result.Count == 0) @@ -4822,12 +4772,11 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses ps.Commands.Clear(); ps.Streams.ClearStreams(); - ps.AddCommand(psRemoteUtilsName); + ps.AddCommand(CopyFileRemoteUtils.PSCopyRemoteUtilsName); } else { - string remoteScript = CopyFileRemoteUtils.PSValidatePathDefinition; - ps.AddScript(remoteScript); + ps.AddScript(CopyFileRemoteUtils.PSValidatePathDefinition); } ps.AddParameter("pathToValidate", path); @@ -4900,7 +4849,7 @@ private string ValidateRemotePathAndGetRoot(string path, Runspaces.PSSession ses return root; } - private bool isValidSession(PSSession session, CmdletProviderContext context, out PSLanguageMode? languageMode) + private static bool isValidSession(PSSession session, CmdletProviderContext context, out PSLanguageMode? languageMode) { // session == null is validated by the parameter binding if (session.Availability != RunspaceAvailability.Available) diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index 23e9fdc0475..d01d4c63f5d 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; + const string CharactersInvalidInDriveName = ":/\\.~"; - do - { - if (string.IsNullOrEmpty(name)) - { - result = false; - break; - } - - if (name.IndexOfAny(s_charactersInvalidInDriveName) >= 0) - { - result = false; - break; - } - } while (false); - - return result; + return !string.IsNullOrEmpty(name) + && name.AsSpan().IndexOfAny(CharactersInvalidInDriveName) < 0; } - private static 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 @@ -1204,8 +1188,7 @@ internal void RemoveDrive( else { PSInvalidOperationException e = - (PSInvalidOperationException) - PSTraceSource.NewInvalidOperationException( + (PSInvalidOperationException)PSTraceSource.NewInvalidOperationException( SessionStateStrings.DriveRemovalPreventedByProvider, drive.Name, drive.Provider); @@ -1451,4 +1434,3 @@ internal PSDriveInfo CurrentDrive } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs b/src/System.Management.Automation/engine/SessionStateFunctionAPIs.cs index 42d3607cadb..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; @@ -116,7 +117,10 @@ internal IDictionary GetFunctionTableAtScope(string scopeI /// internal bool FunctionsExportedWithWildcard { - get { return _functionsExportedWithWildcard; } + get + { + return _functionsExportedWithWildcard; + } set { @@ -181,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)) || @@ -220,7 +224,7 @@ internal FunctionInfo GetFunction(string name) return GetFunction(name, CommandOrigin.Internal); } - private IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) + private static IEnumerable GetFunctionAliases(IParameterMetadataProvider ipmp) { if (ipmp == null || ipmp.Body.ParamBlock == null) yield break; diff --git a/src/System.Management.Automation/engine/SessionStateItem.cs b/src/System.Management.Automation/engine/SessionStateItem.cs index 3d5262bf6c0..173043515ba 100644 --- a/src/System.Management.Automation/engine/SessionStateItem.cs +++ b/src/System.Management.Automation/engine/SessionStateItem.cs @@ -1375,4 +1375,3 @@ private object InvokeDefaultActionDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 4cd4718aca5..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) { @@ -799,7 +796,7 @@ CmdletProviderContext normalizePathContext /// /// A stack of the most recently pushed locations. /// - private Dictionary> _workingLocationStack; + private readonly Dictionary> _workingLocationStack; private const string startingDefaultStackName = "default"; /// @@ -1115,4 +1112,3 @@ internal LocationChangedEventArgs(SessionState sessionState, PathInfo oldPath, P public SessionState SessionState { get; internal set; } } } - diff --git a/src/System.Management.Automation/engine/SessionStateNavigation.cs b/src/System.Management.Automation/engine/SessionStateNavigation.cs index 4afb9218dd7..d2d27ec2d83 100644 --- a/src/System.Management.Automation/engine/SessionStateNavigation.cs +++ b/src/System.Management.Automation/engine/SessionStateNavigation.cs @@ -190,7 +190,7 @@ internal string GetParentPath( } } - private string AddQualifier(string path, ProviderInfo provider, string qualifier, bool isProviderQualified, bool isDriveQualified) + private static string AddQualifier(string path, ProviderInfo provider, string qualifier, bool isProviderQualified, bool isDriveQualified) { string result = path; @@ -640,7 +640,7 @@ internal string NormalizeRelativePath( /// /// The character to test. /// True if the character is a path separator. - private bool IsPathSeparator(char c) + private static bool IsPathSeparator(char c) { return c == StringLiterals.DefaultPathSeparator || c == StringLiterals.AlternatePathSeparator; } @@ -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. @@ -1749,4 +1748,3 @@ private object MoveItemDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProperty.cs b/src/System.Management.Automation/engine/SessionStateProperty.cs index d4568d2ebb3..9f621fa2f7a 100644 --- a/src/System.Management.Automation/engine/SessionStateProperty.cs +++ b/src/System.Management.Automation/engine/SessionStateProperty.cs @@ -1116,4 +1116,3 @@ private object ClearPropertyDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs index f1b637a1d84..3ad2ed99e31 100644 --- a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs @@ -52,7 +52,7 @@ internal Dictionary ProvidersCurrentWorkingDrive } } - private Dictionary _providersCurrentWorkingDrive = new Dictionary(); + private readonly Dictionary _providersCurrentWorkingDrive = new Dictionary(); /// /// Entrypoint used by to add a provider to the current session state @@ -317,7 +317,7 @@ private static string GetPossibleMatches(Collection matchingProvid foreach (ProviderInfo matchingProvider in matchingProviders) { - possibleMatches.Append(" "); + possibleMatches.Append(' '); possibleMatches.Append(matchingProvider.FullName); } @@ -350,10 +350,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - DriveCmdletProvider driveCmdletProvider = - GetProviderInstance(providerId) as DriveCmdletProvider; - - if (driveCmdletProvider == null) + if (GetProviderInstance(providerId) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -385,10 +382,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - DriveCmdletProvider driveCmdletProvider = - GetProviderInstance(provider) as DriveCmdletProvider; - - if (driveCmdletProvider == null) + if (GetProviderInstance(provider) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -420,10 +414,7 @@ private static DriveCmdletProvider GetDriveProviderInstance(CmdletProvider provi throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - DriveCmdletProvider driveCmdletProvider = - providerInstance as DriveCmdletProvider; - - if (driveCmdletProvider == null) + if (providerInstance is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -458,10 +449,7 @@ internal ItemCmdletProvider GetItemProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - ItemCmdletProvider itemCmdletProvider = - GetProviderInstance(providerId) as ItemCmdletProvider; - - if (itemCmdletProvider == null) + if (GetProviderInstance(providerId) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -493,10 +481,7 @@ internal ItemCmdletProvider GetItemProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - ItemCmdletProvider itemCmdletProvider = - GetProviderInstance(provider) as ItemCmdletProvider; - - if (itemCmdletProvider == null) + if (GetProviderInstance(provider) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -528,10 +513,7 @@ private static ItemCmdletProvider GetItemProviderInstance(CmdletProvider provide throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - ItemCmdletProvider itemCmdletProvider = - providerInstance as ItemCmdletProvider; - - if (itemCmdletProvider == null) + if (providerInstance is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -566,10 +548,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - ContainerCmdletProvider containerCmdletProvider = - GetProviderInstance(providerId) as ContainerCmdletProvider; - - if (containerCmdletProvider == null) + if (GetProviderInstance(providerId) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -601,10 +580,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(ProviderInfo provi throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - ContainerCmdletProvider containerCmdletProvider = - GetProviderInstance(provider) as ContainerCmdletProvider; - - if (containerCmdletProvider == null) + if (GetProviderInstance(provider) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -636,10 +612,7 @@ private static ContainerCmdletProvider GetContainerProviderInstance(CmdletProvid throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - ContainerCmdletProvider containerCmdletProvider = - providerInstance as ContainerCmdletProvider; - - if (containerCmdletProvider == null) + if (providerInstance is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -671,10 +644,7 @@ internal NavigationCmdletProvider GetNavigationProviderInstance(ProviderInfo pro throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - NavigationCmdletProvider navigationCmdletProvider = - GetProviderInstance(provider) as NavigationCmdletProvider; - - if (navigationCmdletProvider == null) + if (GetProviderInstance(provider) is not NavigationCmdletProvider navigationCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.NavigationCmdletProvider_NotSupported); @@ -990,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/SessionStatePublic.cs b/src/System.Management.Automation/engine/SessionStatePublic.cs index 0dca57054db..ce14d4948dc 100644 --- a/src/System.Management.Automation/engine/SessionStatePublic.cs +++ b/src/System.Management.Automation/engine/SessionStatePublic.cs @@ -88,7 +88,7 @@ public SessionState() /// public DriveManagementIntrinsics Drive { - get { return _drive ?? (_drive = new DriveManagementIntrinsics(_sessionState)); } + get { return _drive ??= new DriveManagementIntrinsics(_sessionState); } } /// @@ -96,7 +96,7 @@ public DriveManagementIntrinsics Drive /// public CmdletProviderManagementIntrinsics Provider { - get { return _provider ?? (_provider = new CmdletProviderManagementIntrinsics(_sessionState)); } + get { return _provider ??= new CmdletProviderManagementIntrinsics(_sessionState); } } /// @@ -104,7 +104,7 @@ public CmdletProviderManagementIntrinsics Provider /// public PathIntrinsics Path { - get { return _path ?? (_path = new PathIntrinsics(_sessionState)); } + get { return _path ??= new PathIntrinsics(_sessionState); } } /// @@ -112,7 +112,7 @@ public PathIntrinsics Path /// public PSVariableIntrinsics PSVariable { - get { return _variable ?? (_variable = new PSVariableIntrinsics(_sessionState)); } + get { return _variable ??= new PSVariableIntrinsics(_sessionState); } } /// @@ -311,7 +311,7 @@ internal SessionStateInternal Internal #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; private DriveManagementIntrinsics _drive; private CmdletProviderManagementIntrinsics _provider; private PathIntrinsics _path; @@ -336,6 +336,7 @@ public enum SessionStateEntryVisibility Private = 1 } +#nullable enable internal interface IHasSessionStateEntryVisibility { SessionStateEntryVisibility Visibility { get; set; } @@ -371,4 +372,3 @@ public enum PSLanguageMode ConstrainedLanguage = 3 } } - diff --git a/src/System.Management.Automation/engine/SessionStateScope.cs b/src/System.Management.Automation/engine/SessionStateScope.cs index 54b7a40e1f2..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 { @@ -64,7 +65,10 @@ internal SessionStateScope(SessionStateScope parentScope) /// internal SessionStateScope ScriptScope { - get { return _scriptScope; } + get + { + return _scriptScope; + } set { @@ -111,7 +115,7 @@ internal SessionStateScope ScriptScope /// the provider has already been notified. /// /// - /// If is null. + /// If is null. /// /// /// If a drive of the same name already exists in this scope. @@ -165,7 +169,7 @@ internal void NewDrive(PSDriveInfo newDrive) /// by the provider. /// /// - /// If is null. + /// If is null. /// internal void RemoveDrive(PSDriveInfo drive) { @@ -216,7 +220,7 @@ internal void RemoveAllDrives() /// exists in this scope or null if one does not exist. /// /// - /// If is null. + /// If is null. /// internal PSDriveInfo GetDrive(string name) { @@ -418,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) { @@ -489,7 +496,7 @@ internal PSVariable SetVariable(string name, object value, bool asValue, bool fo } else { - variable = (LocalsTuple != null ? LocalsTuple.TrySetVariable(name, value) : null) ?? new PSVariable(name, value); + variable = (LocalsTuple?.TrySetVariable(name, value)) ?? new PSVariable(name, value); } if (ExecutionContext.HasEverUsedConstrainedLanguage) @@ -1242,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; @@ -1254,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; @@ -1615,26 +1626,31 @@ internal Language.TypeResolutionState TypeResolutionState return Parent != null ? Parent.TypeResolutionState : Language.TypeResolutionState.UsingSystem; } - set { _typeResolutionState = value; } + set + { + _typeResolutionState = value; + } } internal IDictionary TypeTable { get; private set; } 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; } @@ -1675,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; } @@ -1692,7 +1714,7 @@ private static FunctionInfo CreateFunction(string name, ScriptBlock function, Fu // performance degradation, so we use lazy initialization for all of them. private Dictionary GetDrives() { - return _drives ?? (_drives = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return _drives ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } private Dictionary _drives; @@ -1704,8 +1726,7 @@ private Dictionary GetDrives() // performance degradation, so we use lazy initialization for all of them. private Dictionary GetAutomountedDrives() { - return _automountedDrives ?? - (_automountedDrives = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return _automountedDrives ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } private Dictionary _automountedDrives; @@ -1852,7 +1873,6 @@ private Dictionary GetAllScopeFunctions() /// table. The entries in this table are automatically propagated /// to new scopes. /// - private readonly Dictionary> _allScopeCmdlets = new Dictionary>(StringComparer.OrdinalIgnoreCase); /// @@ -1891,7 +1911,7 @@ private Dictionary GetAllScopeFunctions() #region Alias mapping - private Dictionary> _commandsToAliasesCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary> _commandsToAliasesCache = new Dictionary>(StringComparer.OrdinalIgnoreCase); /// /// Gets the aliases by command name (used by metadata-driven help) @@ -1965,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 @@ -1981,4 +2011,3 @@ private void CheckVariableChangeInConstrainedLanguage(PSVariable variable) #endregion } } - diff --git a/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs b/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs index c3b643e4f31..e64137cf396 100644 --- a/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs +++ b/src/System.Management.Automation/engine/SessionStateScopeEnumerator.cs @@ -101,4 +101,3 @@ public void Dispose() private SessionStateScope _currentEnumeratedScope; } } - diff --git a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs index 73cf89cebdf..08635c7efe1 100644 --- a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs +++ b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs @@ -35,10 +35,7 @@ internal static ISecurityDescriptorCmdletProvider GetPermissionProviderInstance( throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - ISecurityDescriptorCmdletProvider permissionCmdletProvider = - providerInstance as ISecurityDescriptorCmdletProvider; - - if (permissionCmdletProvider == null) + if (providerInstance is not ISecurityDescriptorCmdletProvider permissionCmdletProvider) { throw PSTraceSource.NewNotSupportedException( @@ -579,4 +576,3 @@ internal ObjectSecurity NewSecurityDescriptorOfType( #endregion NewSecurityDescriptor } } - diff --git a/src/System.Management.Automation/engine/SessionStateUtils.cs b/src/System.Management.Automation/engine/SessionStateUtils.cs index 7c684abd1c3..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; @@ -325,4 +322,3 @@ public enum OpenMode Overwrite } } - diff --git a/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs b/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs index 9b4028314b8..336c2b77952 100644 --- a/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateVariableAPIs.cs @@ -181,7 +181,7 @@ internal object GetVariableValue(string name, object defaultValue) /// The variable if it was found or null if it was not. /// /// - /// The is first parsed to see if it contains a drive + /// The is first parsed to see if it contains a drive /// specifier or special scope. If a special scope is found ("LOCAL" or "GLOBAL") /// then only that scope is searched for the variable. If any other drive specifier /// is found the lookup goes in the following order. @@ -252,7 +252,7 @@ internal object GetVariableValue( /// The variable if it was found or null if it was not. /// /// - /// The is first parsed to see if it contains a drive + /// The is first parsed to see if it contains a drive /// specifier or special scope. If a special scope is found ("LOCAL" or "GLOBAL") /// then only that scope is searched for the variable. If any other drive specifier /// is found the lookup goes in the following order. @@ -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( @@ -523,7 +519,7 @@ internal object GetVariableValueFromProvider( /// The variable if it was found or null if it was not. /// /// - /// The is first parsed to see if it contains a drive + /// The is first parsed to see if it contains a drive /// specifier or special scope. If a special scope is found ("LOCAL" or "GLOBAL") /// then only that scope is searched for the variable. /// - current scope @@ -575,7 +571,7 @@ internal PSVariable GetVariableItem( /// The variable if it was found or null if it was not. /// /// - /// The is first parsed to see if it contains a drive + /// The is first parsed to see if it contains a drive /// specifier or special scope. If a special scope is found ("LOCAL" or "GLOBAL") /// then only that scope is searched for the variable. /// - current scope @@ -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( @@ -1179,7 +1171,7 @@ internal object SetVariable( if (variablePath.IsPrivate && varResult != null) { - varResult.Options = varResult.Options | ScopedItemOptions.Private; + varResult.Options |= ScopedItemOptions.Private; } result = varResult; @@ -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( @@ -1816,7 +1804,7 @@ internal IDictionary GetVariableTable() return result; } - private void GetScopeVariableTable(SessionStateScope scope, Dictionary result, bool includePrivate) + private static void GetScopeVariableTable(SessionStateScope scope, Dictionary result, bool includePrivate) { foreach (KeyValuePair entry in scope.Variables) { @@ -1839,10 +1827,7 @@ private void GetScopeVariableTable(SessionStateScope scope, Dictionary diff --git a/src/System.Management.Automation/engine/ShellVariable.cs b/src/System.Management.Automation/engine/ShellVariable.cs index 4be5a96f52b..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. /// @@ -387,7 +395,7 @@ internal void SetOptions(ScopedItemOptions newOptions, bool force) /// public Collection Attributes { - get { return _attributes ?? (_attributes = new PSVariableAttributeCollection(this)); } + get { return _attributes ??= new PSVariableAttributeCollection(this); } } private PSVariableAttributeCollection _attributes; @@ -759,7 +767,10 @@ public LocalVariable(string name, MutableTuple tuple, int tupleSlot) public override ScopedItemOptions Options { - get { return base.Options; } + get + { + return base.Options; + } set { @@ -793,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) @@ -840,7 +856,7 @@ public override object Value /// public override string Description { - get { return _description ?? (_description = SessionStateStrings.DollarNullDescription); } + get { return _description ??= SessionStateStrings.DollarNullDescription; } set { /* Do nothing */ } } @@ -898,4 +914,3 @@ public enum ScopedItemOptions Unspecified = 0x10 } } - diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index aa241b6c942..51419a0d5c2 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 { @@ -32,10 +33,18 @@ internal static class SpecialVariables internal static readonly VariablePath OFSVarPath = new VariablePath(OFS); + internal const string PSStyle = "PSStyle"; + + internal static readonly VariablePath PSStyleVarPath = new VariablePath(PSStyle); + internal const string OutputEncoding = "OutputEncoding"; internal static readonly VariablePath OutputEncodingVarPath = new VariablePath(OutputEncoding); + internal const string PSApplicationOutputEncoding = nameof(PSApplicationOutputEncoding); + + internal static readonly VariablePath PSApplicationOutputEncodingVarPath = new VariablePath(PSApplicationOutputEncoding); + internal const string VerboseHelpErrors = "VerboseHelpErrors"; internal static readonly VariablePath VerboseHelpErrorsVarPath = new VariablePath(VerboseHelpErrors); @@ -200,6 +209,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"); @@ -217,6 +227,7 @@ internal static class SpecialVariables internal static readonly VariablePath IsCoreCLRPath = new VariablePath("IsCoreCLR"); #endregion + #region Preference Variables internal const string DebugPreference = "DebugPreference"; @@ -253,6 +264,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); @@ -312,46 +333,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 ( @@ -364,6 +392,7 @@ internal static class SpecialVariables SpecialVariables.NestedPromptLevel, SpecialVariables.pwd, SpecialVariables.Matches, + SpecialVariables.PSApplicationOutputEncoding, }, StringComparer.OrdinalIgnoreCase ); @@ -397,5 +426,6 @@ internal enum PreferenceVariable Warning = 13, Information = 14, Confirm = 15, + Progress = 16, } } diff --git a/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs new file mode 100644 index 00000000000..df828c724a2 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +namespace System.Management.Automation.Subsystem +{ + /// + /// Implementation of 'Get-PSSubsystem' cmdlet. + /// + [Cmdlet(VerbsCommon.Get, "PSSubsystem", DefaultParameterSetName = AllSet)] + [OutputType(typeof(SubsystemInfo))] + public sealed class GetPSSubsystemCommand : PSCmdlet + { + private const string AllSet = "GetAllSet"; + private const string TypeSet = "GetByTypeSet"; + private const string KindSet = "GetByKindSet"; + + /// + /// Gets or sets a concrete subsystem kind. + /// + [Parameter(Mandatory = true, ParameterSetName = KindSet, ValueFromPipeline = true)] + public SubsystemKind Kind { get; set; } + + /// + /// Gets or sets the interface or abstract class type of a concrete subsystem. + /// + [Parameter(Mandatory = true, ParameterSetName = TypeSet, ValueFromPipeline = true)] + public Type? SubsystemType { get; set; } + + /// + /// ProcessRecord implementation. + /// + protected override void ProcessRecord() + { + switch (ParameterSetName) + { + case AllSet: + WriteObject(SubsystemManager.GetAllSubsystemInfo(), enumerateCollection: true); + break; + case KindSet: + WriteObject(SubsystemManager.GetSubsystemInfo(Kind)); + break; + case TypeSet: + WriteObject(SubsystemManager.GetSubsystemInfo(SubsystemType!)); + break; + + default: + throw new InvalidOperationException("New parameter set is added but the switch statement is not updated."); + } + } + } +} 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..b1aa781fea7 --- /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: 1000); + } + + /// + /// 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 new file mode 100644 index 00000000000..72b8a55d148 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/ISubsystem.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; + +namespace System.Management.Automation.Subsystem +{ + /// + /// Define the kinds of subsystems. + /// + /// + /// 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, + } + + /// + /// Define the base interface to implement a subsystem. + /// The API contracts for specific subsystems are defined within the specific interfaces/abstract classes that implements this interface. + /// + /// + /// 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 + { + /// + /// Gets the unique identifier for a subsystem implementation. + /// + Guid Id { get; } + + /// + /// Gets the name of a subsystem implementation. + /// + string Name { get; } + + /// + /// Gets the description of a subsystem implementation. + /// + string Description { get; } + + /// + /// Gets a dictionary that contains the functions to be defined at the global scope of a PowerShell session. + /// Key: function name; Value: function script. + /// + Dictionary? FunctionsToDefine { get; } + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs new file mode 100644 index 00000000000..edb46960612 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/CommandPrediction.cs @@ -0,0 +1,281 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Management.Automation.Subsystem.Prediction +{ + /// + /// The class represents the prediction result from a predictor. + /// + public sealed class PredictionResult + { + /// + /// Gets the Id of the predictor. + /// + public Guid Id { get; } + + /// + /// Gets the name of the predictor. + /// + public string Name { get; } + + /// + /// Gets the mini-session id that represents a specific invocation to the API of the predictor. + /// When it's not specified, it's considered by a client that the predictor doesn't expect feedback. + /// + public uint? Session { get; } + + /// + /// Gets the suggestions. + /// + public IReadOnlyList Suggestions { get; } + + internal PredictionResult(Guid id, string name, uint? session, List suggestions) + { + Id = id; + Name = name; + Session = session; + Suggestions = suggestions; + } + } + + /// + /// Provides a set of possible predictions for given input. + /// + public static class CommandPrediction + { + /// + /// Collect the predictive suggestions from registered predictors using the default timeout. + /// + /// Represents the client that initiates the call. + /// 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?> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens) + { + return PredictInputAsync(client, ast, astTokens, millisecondsTimeout: 20); + } + + /// + /// Collect the predictive suggestions from registered predictors using the specified timeout. + /// + /// Represents the client that initiates the call. + /// The object from parsing the current command line input. + /// The objects from parsing the current command line input. + /// The milliseconds to timeout. + /// A list of objects. + public static async Task?> PredictInputAsync(PredictionClient client, Ast ast, Token[] astTokens, int millisecondsTimeout) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(millisecondsTimeout); + + var predictors = SubsystemManager.GetSubsystems(); + if (predictors.Count == 0) + { + return null; + } + + var context = new PredictionContext(ast, astTokens); + 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( + callBack, + predictor, + cancellationSource.Token, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + } + + await Task.WhenAny( + Task.WhenAll(tasks), + Task.Delay(millisecondsTimeout, cancellationSource.Token)).ConfigureAwait(false); + cancellationSource.Cancel(); + + var resultList = new List(predictors.Count); + foreach (Task task in tasks) + { + if (task.IsCompletedSuccessfully) + { + PredictionResult? result = task.Result; + if (result != null) + { + resultList.Add(result); + } + } + } + + 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; + }; + } + } + + /// + /// Allow registered predictors to do early processing when a command line is accepted. + /// + /// Represents the client that initiates the call. + /// History command lines provided as references for prediction. + public static void OnCommandLineAccepted(PredictionClient client, IReadOnlyList 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.CanAcceptFeedback(client, PredictorFeedbackKind.CommandLineExecuted)) + { + 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); + } + } + + /// + /// Send feedback to a predictor when one or more suggestions from it were displayed to the user. + /// + /// Represents the client that initiates the call. + /// The identifier of the predictor whose prediction result was accepted. + /// 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. + /// + public static void OnSuggestionDisplayed(PredictionClient client, Guid predictorId, uint session, int countOrIndex) + { + var predictors = SubsystemManager.GetSubsystems(); + if (predictors.Count == 0) + { + return; + } + + foreach (ICommandPredictor predictor in predictors) + { + if (predictor.Id == predictorId) + { + 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); + } + } + + /// + /// Send feedback to a predictor when a suggestion from it was accepted. + /// + /// Represents the client that initiates the call. + /// 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(PredictionClient client, Guid predictorId, uint session, string suggestionText) + { + ArgumentException.ThrowIfNullOrEmpty(suggestionText); + + var predictors = SubsystemManager.GetSubsystems(); + if (predictors.Count == 0) + { + return; + } + + foreach (ICommandPredictor predictor in predictors) + { + if (predictor.Id == predictorId) + { + 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/PredictionSubsystem/ICommandPredictor.cs b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs new file mode 100644 index 00000000000..275dc1733b6 --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/PredictionSubsystem/ICommandPredictor.cs @@ -0,0 +1,297 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Management.Automation.Internal; +using System.Management.Automation.Language; +using System.Threading; + +namespace System.Management.Automation.Subsystem.Prediction +{ + /// + /// Interface for implementing a predictor plugin. + /// + public interface ICommandPredictor : ISubsystem + { + /// + /// Default implementation. No function is required for a predictor. + /// + Dictionary? ISubsystem.FunctionsToDefine => null; + + /// + /// Get the predictive suggestions. It indicates the start of a suggestion rendering session. + /// + /// 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 accepts a specific kind of feedback. + /// + /// 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; + + /// + /// One or more suggestions provided by the predictor were 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(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. + /// History command lines provided as references for prediction. + void OnCommandLineAccepted(PredictionClient client, IReadOnlyList history) { } + + /// + /// A command line was done execution. + /// + /// 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 + { + /// + /// Feedback when one or more suggestions are displayed to the user. + /// + SuggestionDisplayed, + + /// + /// Feedback when a suggestion is accepted by the user. + /// + 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 sealed class PredictionContext + { + /// + /// Gets the abstract syntax tree (AST) generated from parsing the user input. + /// + public Ast InputAst { get; } + + /// + /// Gets the tokens generated from parsing the user input. + /// + public IReadOnlyList InputTokens { get; } + + /// + /// Gets the cursor position, which is assumed always at the end of the input line. + /// + public IScriptPosition CursorPosition { get; } + + /// + /// Gets the token at the cursor. + /// + public Token? TokenAtCursor { get; } + + /// + /// Gets all ASTs that are related to the cursor position, + /// which is assumed always at the end of the input line. + /// + public IReadOnlyList RelatedAsts { get; } + + /// + /// Initializes a new instance of the class from the AST and tokens that represent the user input. + /// + /// The object from parsing the current command line input. + /// The objects from parsing the current command line input. + public PredictionContext(Ast inputAst, Token[] inputTokens) + { + ArgumentNullException.ThrowIfNull(inputAst); + ArgumentNullException.ThrowIfNull(inputTokens); + + var cursor = inputAst.Extent.EndScriptPosition; + var astContext = CompletionAnalysis.ExtractAstContext(inputAst, inputTokens, cursor); + + InputAst = inputAst; + InputTokens = inputTokens; + CursorPosition = cursor; + TokenAtCursor = astContext.TokenAtCursor; + RelatedAsts = astContext.RelatedAsts; + } + + /// + /// Creates a context instance from the user input line. + /// + /// The user input. + /// A object. + public static PredictionContext Create(string input) + { + ArgumentException.ThrowIfNullOrEmpty(input); + + Ast ast = Parser.ParseInput(input, out Token[] tokens, out _); + return new PredictionContext(ast, tokens); + } + } + + /// + /// The class represents a predictive suggestion generated by a predictor. + /// + public sealed class PredictiveSuggestion + { + /// + /// Gets the suggestion. + /// + public string SuggestionText { get; } + + /// + /// Gets the tooltip of the suggestion. + /// + public string? ToolTip { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The predictive suggestion text. + public PredictiveSuggestion(string suggestion) + : this(suggestion, toolTip: null) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The predictive suggestion text. + /// The tooltip of the suggestion. + public PredictiveSuggestion(string suggestion, string? toolTip) + { + ArgumentException.ThrowIfNullOrEmpty(suggestion); + + SuggestionText = suggestion; + ToolTip = toolTip; + } + } + + /// + /// A package returned from . + /// + public struct SuggestionPackage + { + /// + /// Gets the mini-session that represents a specific invocation to . + /// When it's not specified, it's considered by a client that the predictor doesn't expect feedback. + /// + public uint? Session { get; } + + /// + /// Gets the suggestion entries returned from that mini-session. + /// + public List? SuggestionEntries { get; } + + /// + /// Initializes a new instance of the struct without providing a session id. + /// Note that, when a session id is not specified, it's considered by a client that the predictor doesn't expect feedback. + /// + /// The suggestions to return. + public SuggestionPackage(List suggestionEntries) + { + Requires.NotNullOrEmpty(suggestionEntries, nameof(suggestionEntries)); + + Session = null; + SuggestionEntries = suggestionEntries; + } + + /// + /// Initializes a new instance of the struct with the mini-session id and the suggestions. + /// + /// The mini-session where suggestions came from. + /// The suggestions to return. + public SuggestionPackage(uint session, List suggestionEntries) + { + Requires.NotNullOrEmpty(suggestionEntries, nameof(suggestionEntries)); + + Session = session; + SuggestionEntries = suggestionEntries; + } + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs new file mode 100644 index 00000000000..8756fd69c9b --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemInfo.cs @@ -0,0 +1,361 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation.Internal; +using Microsoft.PowerShell.Telemetry; + +namespace System.Management.Automation.Subsystem +{ + /// + /// Class used to represent the metadata and state of a subsystem. + /// + public abstract class SubsystemInfo + { + #region "Metadata of a Subsystem (public)" + + /// + /// Gets the kind of a concrete subsystem. + /// + public SubsystemKind Kind { get; } + + /// + /// Gets the type of a concrete subsystem. + /// + public Type SubsystemType { get; } + + /// + /// Gets a value indicating whether the subsystem allows to unregister an implementation. + /// + public bool AllowUnregistration { get; private set; } + + /// + /// Gets a value indicating whether the subsystem allows to have multiple implementations registered. + /// + public bool AllowMultipleRegistration { get; private set; } + + /// + /// Gets the names of the required cmdlets that have to be implemented by the subsystem implementation. + /// + public ReadOnlyCollection RequiredCmdlets { get; private set; } + + /// + /// Gets the names of the required functions that have to be implemented by the subsystem implementation. + /// + public ReadOnlyCollection RequiredFunctions { get; private set; } + + // /// + // /// A subsystem may depend on or more other subsystems. + // /// Maybe add a 'DependsOn' member? + // /// This can be validated when registering a subsystem implementation, + // /// to make sure its prerequisites have already been registered. + // /// + // public ReadOnlyCollection DependsOn { get; private set; } + + #endregion + + #region "State of a Subsystem (public)" + + /// + /// Indicate whether there is any implementation registered to the subsystem. + /// + public bool IsRegistered => _cachedImplInfos.Count > 0; + + /// + /// Get the information about the registered implementations. + /// + public ReadOnlyCollection Implementations => _cachedImplInfos; + + #endregion + + #region "private/internal instance members" + + private protected readonly object _syncObj; + private protected ReadOnlyCollection _cachedImplInfos; + + private protected SubsystemInfo(SubsystemKind kind, Type subsystemType) + { + _syncObj = new object(); + _cachedImplInfos = Utils.EmptyReadOnlyCollection(); + + Kind = kind; + SubsystemType = subsystemType; + AllowUnregistration = false; + AllowMultipleRegistration = false; + RequiredCmdlets = Utils.EmptyReadOnlyCollection(); + RequiredFunctions = Utils.EmptyReadOnlyCollection(); + } + + private protected abstract void AddImplementation(ISubsystem rawImpl); + + private protected abstract ISubsystem RemoveImplementation(Guid id); + + internal void RegisterImplementation(ISubsystem impl) + { + AddImplementation(impl); + ApplicationInsightsTelemetry.SendUseTelemetry(ApplicationInsightsTelemetry.s_subsystemRegistration, impl.Name); + } + + internal ISubsystem UnregisterImplementation(Guid id) + { + return RemoveImplementation(id); + } + + #endregion + + #region "Static factory overloads" + + internal static SubsystemInfo Create(SubsystemKind kind) + where TConcreteSubsystem : class, ISubsystem + { + return new SubsystemInfoImpl(kind); + } + + internal static SubsystemInfo Create( + SubsystemKind kind, + bool allowUnregistration, + bool allowMultipleRegistration) where TConcreteSubsystem : class, ISubsystem + { + return new SubsystemInfoImpl(kind) + { + AllowUnregistration = allowUnregistration, + AllowMultipleRegistration = allowMultipleRegistration, + }; + } + + internal static SubsystemInfo Create( + SubsystemKind kind, + bool allowUnregistration, + bool allowMultipleRegistration, + ReadOnlyCollection requiredCmdlets, + ReadOnlyCollection requiredFunctions) where TConcreteSubsystem : class, ISubsystem + { + if (allowMultipleRegistration && + (requiredCmdlets.Count > 0 || requiredFunctions.Count > 0)) + { + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.InvalidSubsystemInfo, + kind.ToString())); + } + + return new SubsystemInfoImpl(kind) + { + AllowUnregistration = allowUnregistration, + AllowMultipleRegistration = allowMultipleRegistration, + RequiredCmdlets = requiredCmdlets, + RequiredFunctions = requiredFunctions, + }; + } + + #endregion + + #region "ImplementationInfo" + + /// + /// Information about an implementation of a subsystem. + /// + public class ImplementationInfo + { + internal ImplementationInfo(SubsystemKind kind, ISubsystem implementation) + { + Id = implementation.Id; + Kind = kind; + Name = implementation.Name; + Description = implementation.Description; + ImplementationType = implementation.GetType(); + } + + /// + /// Gets the unique identifier for a subsystem implementation. + /// + public Guid Id { get; } + + /// + /// Gets the kind of subsystem. + /// + public SubsystemKind Kind { get; } + + /// + /// Gets the name of a subsystem implementation. + /// + public string Name { get; } + + /// + /// Gets the description of a subsystem implementation. + /// + public string Description { get; } + + /// + /// Gets the implementation type. + /// + public Type ImplementationType { get; } + } + + #endregion + } + + internal sealed class SubsystemInfoImpl : SubsystemInfo + where TConcreteSubsystem : class, ISubsystem + { + private ReadOnlyCollection _registeredImpls; + + internal SubsystemInfoImpl(SubsystemKind kind) + : base(kind, typeof(TConcreteSubsystem)) + { + _registeredImpls = Utils.EmptyReadOnlyCollection(); + } + + /// + /// The 'add' and 'remove' operations are implemented in a way to optimize the 'reading' operation, + /// so that reading is lock-free and allocation-free, at the cost of O(n) copy in 'add' and 'remove' + /// ('n' is the number of registered implementations). + /// + /// + /// 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) + { + var impl = (TConcreteSubsystem)rawImpl; + + if (_registeredImpls.Count == 0) + { + _registeredImpls = new ReadOnlyCollection(new[] { impl }); + _cachedImplInfos = new ReadOnlyCollection(new[] { new ImplementationInfo(Kind, impl) }); + return; + } + + if (!AllowMultipleRegistration) + { + throw new InvalidOperationException( + StringUtil.Format( + SubsystemStrings.MultipleRegistrationNotAllowed, + Kind.ToString())); + } + + foreach (TConcreteSubsystem item in _registeredImpls) + { + if (item.Id == impl.Id) + { + throw new InvalidOperationException( + StringUtil.Format( + SubsystemStrings.ImplementationAlreadyRegistered, + impl.Id, + Kind.ToString())); + } + } + + int newCapacity = _registeredImpls.Count + 1; + var implList = new List(newCapacity); + implList.AddRange(_registeredImpls); + implList.Add(impl); + + var implInfo = new List(newCapacity); + implInfo.AddRange(_cachedImplInfos); + implInfo.Add(new ImplementationInfo(Kind, impl)); + + _registeredImpls = new ReadOnlyCollection(implList); + _cachedImplInfos = new ReadOnlyCollection(implInfo); + } + } + + /// + /// The 'add' and 'remove' operations are implemented in a way to optimize the 'reading' operation, + /// so that reading is lock-free and allocation-free, at the cost of O(n) copy in 'add' and 'remove' + /// ('n' is the number of registered implementations). + /// + /// + /// 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) + { + throw new InvalidOperationException( + StringUtil.Format( + SubsystemStrings.UnregistrationNotAllowed, + Kind.ToString())); + } + + lock (_syncObj) + { + if (_registeredImpls.Count == 0) + { + throw new InvalidOperationException( + StringUtil.Format( + SubsystemStrings.NoImplementationRegistered, + Kind.ToString())); + } + + int index = -1; + for (int i = 0; i < _registeredImpls.Count; i++) + { + if (_registeredImpls[i].Id == id) + { + index = i; + break; + } + } + + if (index == -1) + { + throw new InvalidOperationException( + StringUtil.Format( + SubsystemStrings.ImplementationNotFound, + id.ToString())); + } + + ISubsystem target = _registeredImpls[index]; + if (_registeredImpls.Count == 1) + { + _registeredImpls = Utils.EmptyReadOnlyCollection(); + _cachedImplInfos = Utils.EmptyReadOnlyCollection(); + } + else + { + 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) + { + continue; + } + + implList.Add(_registeredImpls[i]); + implInfo.Add(_cachedImplInfos[i]); + } + + _registeredImpls = new ReadOnlyCollection(implList); + _cachedImplInfos = new ReadOnlyCollection(implInfo); + } + + return target; + } + } + + internal TConcreteSubsystem? GetImplementation() + { + var localRef = _registeredImpls; + return localRef.Count > 0 ? localRef[localRef.Count - 1] : null; + } + + internal ReadOnlyCollection GetAllImplementations() + { + return _registeredImpls; + } + } +} diff --git a/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs new file mode 100644 index 00000000000..e389040899e --- /dev/null +++ b/src/System.Management.Automation/engine/Subsystem/SubsystemManager.cs @@ -0,0 +1,309 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +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 +{ + /// + /// Class used to manage subsystems. + /// + public static class SubsystemManager + { + private static readonly ReadOnlyCollection s_subsystems; + private static readonly ReadOnlyDictionary s_subSystemTypeMap; + private static readonly ReadOnlyDictionary s_subSystemKindMap; + + static SubsystemManager() + { + var subsystems = new SubsystemInfo[] + { + SubsystemInfo.Create( + 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); + var subSystemKindMap = new Dictionary(subsystems.Length); + + foreach (var subsystem in subsystems) + { + subSystemTypeMap.Add(subsystem.SubsystemType, subsystem); + subSystemKindMap.Add(subsystem.Kind, subsystem); + } + + 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 + + /// + /// Get the proxy object registered for a specific subsystem. + /// Return null when the given subsystem is not registered. + /// + /// + /// Design point: + /// 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 implementation object of the concrete subsystem. + internal static TConcreteSubsystem? GetSubsystem() + where TConcreteSubsystem : class, ISubsystem + { + if (s_subSystemTypeMap.TryGetValue(typeof(TConcreteSubsystem), out SubsystemInfo? subsystemInfo)) + { + var subsystemInfoImpl = (SubsystemInfoImpl)subsystemInfo; + return subsystemInfoImpl.GetImplementation(); + } + + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.SubsystemTypeUnknown, + typeof(TConcreteSubsystem).FullName)); + } + + /// + /// Get all the proxy objects registered for a specific subsystem. + /// Return an empty collection when the given subsystem is not registered. + /// + /// The concrete subsystem base type. + /// A readonly collection of all implementation objects registered for the concrete subsystem. + internal static ReadOnlyCollection GetSubsystems() + where TConcreteSubsystem : class, ISubsystem + { + if (s_subSystemTypeMap.TryGetValue(typeof(TConcreteSubsystem), out SubsystemInfo? subsystemInfo)) + { + var subsystemInfoImpl = (SubsystemInfoImpl)subsystemInfo; + return subsystemInfoImpl.GetAllImplementations(); + } + + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.SubsystemTypeUnknown, + typeof(TConcreteSubsystem).FullName)); + } + + #endregion + + #region public - Subsystem metadata + + /// + /// Get the information about all subsystems. + /// + /// A readonly collection of all objects. + public static ReadOnlyCollection GetAllSubsystemInfo() + { + return s_subsystems; + } + + /// + /// Get the information about a subsystem by the subsystem type. + /// + /// The base type of a specific concrete subsystem. + /// The object that represents the concrete subsystem. + public static SubsystemInfo GetSubsystemInfo(Type subsystemType) + { + ArgumentNullException.ThrowIfNull(subsystemType); + + if (s_subSystemTypeMap.TryGetValue(subsystemType, out SubsystemInfo? subsystemInfo)) + { + return subsystemInfo; + } + + throw new ArgumentException( + subsystemType == typeof(ISubsystem) + ? SubsystemStrings.MustUseConcreteSubsystemType + : StringUtil.Format( + SubsystemStrings.SubsystemTypeUnknown, + subsystemType.FullName), + nameof(subsystemType)); + } + + /// + /// Get the information about a subsystem by the subsystem kind. + /// + /// A specific . + /// The object that represents the concrete subsystem. + public static SubsystemInfo GetSubsystemInfo(SubsystemKind kind) + { + if (s_subSystemKindMap.TryGetValue(kind, out SubsystemInfo? subsystemInfo)) + { + return subsystemInfo; + } + + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.SubsystemKindUnknown, + kind.ToString()), + nameof(kind)); + } + + #endregion + + #region public - Subsystem registration + + /// + /// Subsystem registration. + /// + /// The concrete subsystem base type. + /// The implementation type of that concrete subsystem. + /// An instance of the implementation. + public static void RegisterSubsystem(TImplementation proxy) + where TConcreteSubsystem : class, ISubsystem + where TImplementation : class, TConcreteSubsystem + { + ArgumentNullException.ThrowIfNull(proxy); + + RegisterSubsystem(GetSubsystemInfo(typeof(TConcreteSubsystem)), proxy); + } + + /// + /// Register an implementation for a subsystem. + /// + /// The target of the registration. + /// An instance of the implementation. + public static void RegisterSubsystem(SubsystemKind kind, ISubsystem proxy) + { + ArgumentNullException.ThrowIfNull(proxy); + + SubsystemInfo info = GetSubsystemInfo(kind); + if (!info.SubsystemType.IsAssignableFrom(proxy.GetType())) + { + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.ConcreteSubsystemNotImplemented, + kind.ToString(), + info.SubsystemType.Name), + nameof(proxy)); + } + + RegisterSubsystem(info, proxy); + } + + private static void RegisterSubsystem(SubsystemInfo subsystemInfo, ISubsystem proxy) + { + if (proxy.Id == Guid.Empty) + { + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.EmptyImplementationId, + subsystemInfo.Kind.ToString()), + nameof(proxy)); + } + + if (string.IsNullOrEmpty(proxy.Name)) + { + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.NullOrEmptyImplementationName, + subsystemInfo.Kind.ToString()), + nameof(proxy)); + } + + if (string.IsNullOrEmpty(proxy.Description)) + { + throw new ArgumentException( + StringUtil.Format( + SubsystemStrings.NullOrEmptyImplementationDescription, + subsystemInfo.Kind.ToString()), + nameof(proxy)); + } + + if (subsystemInfo.RequiredCmdlets.Count > 0 || subsystemInfo.RequiredFunctions.Count > 0) + { + // Process 'proxy.CmdletImplementationAssembly' and 'proxy.FunctionsToDefine' + // Functions are added to global scope. + // Cmdlets are loaded in a way like a snapin, making the 'Source' of the cmdlets to be 'Microsoft.PowerShell.Core'. + // + // For example, let's say the Job adapter is made a subsystem, then all `*-Job` cmdlets will be moved out of S.M.A + // into a subsystem implementation DLL. After registration, all `*-Job` cmdlets should be back in the + // 'Microsoft.PowerShell.Core' namespace to keep backward compatibility. + // + // Both cmdlets and functions are added to the default InitialSessionState used for creating a new Runspace, + // so the subsystem works for all subsequent new runspaces after it's registered. + // Take the Job adapter subsystem as an instance again, so when creating another Runspace after the registration, + // all '*-Job' cmdlets should be available in the 'Microsoft.PowerShell.Core' namespace by default. + } + + subsystemInfo.RegisterImplementation(proxy); + } + + #endregion + + #region public - Subsystem unregistration + + /// + /// Subsystem unregistration. + /// Throw 'InvalidOperationException' when called for subsystems that cannot be unregistered. + /// + /// The base type of the target concrete subsystem of the un-registration. + /// The Id of the implementation to be unregistered. + public static void UnregisterSubsystem(Guid id) + where TConcreteSubsystem : class, ISubsystem + { + UnregisterSubsystem(GetSubsystemInfo(typeof(TConcreteSubsystem)), id); + } + + /// + /// Subsystem unregistration. + /// Throw 'InvalidOperationException' when called for subsystems that cannot be unregistered. + /// + /// The target of the un-registration. + /// The Id of the implementation to be unregistered. + public static void UnregisterSubsystem(SubsystemKind kind, Guid id) + { + UnregisterSubsystem(GetSubsystemInfo(kind), id); + } + + private static void UnregisterSubsystem(SubsystemInfo subsystemInfo, Guid id) + { + if (subsystemInfo.RequiredCmdlets.Count > 0 || subsystemInfo.RequiredFunctions.Count > 0) + { + throw new NotSupportedException("NotSupported yet: unregister subsystem that introduced new cmdlets/functions."); + } + + ISubsystem impl = subsystemInfo.UnregisterImplementation(id); + if (impl is IDisposable disposable) + { + try + { + disposable.Dispose(); + } + catch + { + // It's OK to ignore all exceptions when disposing the object. + } + } + } + + #endregion + } +} diff --git a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs index 9840aaa0555..d77e700e90e 100644 --- a/src/System.Management.Automation/engine/ThirdPartyAdapter.cs +++ b/src/System.Management.Automation/engine/ThirdPartyAdapter.cs @@ -279,7 +279,7 @@ protected override string PropertyType(PSProperty property, bool forDisplay) return propertyTypeName ?? "System.Object"; } - private PSPropertyAdapter _externalAdapter; + private readonly PSPropertyAdapter _externalAdapter; } /// @@ -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(); @@ -351,7 +348,7 @@ public virtual Collection GetTypeNameHierarchy(object baseObject) /// /// Returns a property if it's name matches the specified , otherwise null. /// - /// An adapted property if the predicate matches, or null. + /// An adapted property if the predicate matches, or . public virtual PSAdaptedProperty GetFirstPropertyOrDefault(object baseObject, MemberNamePredicate predicate) { foreach (var property in GetProperties(baseObject)) diff --git a/src/System.Management.Automation/engine/TransactedString.cs b/src/System.Management.Automation/engine/TransactedString.cs index 948cf1d9d81..05eab93d198 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(); @@ -195,4 +195,3 @@ private void ValidateTransactionOrEnlist() } } } - diff --git a/src/System.Management.Automation/engine/TransactionManager.cs b/src/System.Management.Automation/engine/TransactionManager.cs index bf86d90e812..e77ffebedb4 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) { @@ -706,4 +706,3 @@ public void Dispose(bool disposing) } } } - diff --git a/src/System.Management.Automation/engine/TypeMetadata.cs b/src/System.Management.Automation/engine/TypeMetadata.cs index 192042a3def..926b07fb15a 100644 --- a/src/System.Management.Automation/engine/TypeMetadata.cs +++ b/src/System.Management.Automation/engine/TypeMetadata.cs @@ -265,23 +265,23 @@ internal ParameterFlags Flags get { ParameterFlags flags = 0; - if (IsMandatory) { flags = flags | ParameterFlags.Mandatory; } + if (IsMandatory) { flags |= ParameterFlags.Mandatory; } - if (ValueFromPipeline) { flags = flags | ParameterFlags.ValueFromPipeline; } + if (ValueFromPipeline) { flags |= ParameterFlags.ValueFromPipeline; } - if (ValueFromPipelineByPropertyName) { flags = flags | ParameterFlags.ValueFromPipelineByPropertyName; } + if (ValueFromPipelineByPropertyName) { flags |= ParameterFlags.ValueFromPipelineByPropertyName; } - if (ValueFromRemainingArguments) { flags = flags | ParameterFlags.ValueFromRemainingArguments; } + if (ValueFromRemainingArguments) { flags |= ParameterFlags.ValueFromRemainingArguments; } return flags; } set { - this.IsMandatory = (ParameterFlags.Mandatory == (value & ParameterFlags.Mandatory)); - this.ValueFromPipeline = (ParameterFlags.ValueFromPipeline == (value & ParameterFlags.ValueFromPipeline)); - this.ValueFromPipelineByPropertyName = (ParameterFlags.ValueFromPipelineByPropertyName == (value & ParameterFlags.ValueFromPipelineByPropertyName)); - this.ValueFromRemainingArguments = (ParameterFlags.ValueFromRemainingArguments == (value & ParameterFlags.ValueFromRemainingArguments)); + this.IsMandatory = ((value & ParameterFlags.Mandatory) == ParameterFlags.Mandatory); + this.ValueFromPipeline = ((value & ParameterFlags.ValueFromPipeline) == ParameterFlags.ValueFromPipeline); + this.ValueFromPipelineByPropertyName = ((value & ParameterFlags.ValueFromPipelineByPropertyName) == ParameterFlags.ValueFromPipelineByPropertyName); + this.ValueFromRemainingArguments = ((value & ParameterFlags.ValueFromRemainingArguments) == ParameterFlags.ValueFromRemainingArguments); } } @@ -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()]"; @@ -895,7 +897,7 @@ internal string GetProxyParameterData(string prefix, string paramNameOverride, b /// /// Attribute's proxy string. /// - private string GetProxyAttributeData(Attribute attrib, string prefix) + private static string GetProxyAttributeData(Attribute attrib, string prefix) { string result; @@ -931,6 +933,10 @@ private string GetProxyAttributeData(Attribute attrib, string prefix) { format = ValidateRangeFloatFormat; } + else if (rangeType.IsEnum) + { + format = ValidateRangeEnumFormat; + } else { format = ValidateRangeFormat; @@ -941,7 +947,8 @@ private string GetProxyAttributeData(Attribute attrib, string prefix) format, prefix, validRangeAttrib.MinRange, - validRangeAttrib.MaxRange); + validRangeAttrib.MaxRange, + rangeType.FullName); return result; } } @@ -976,9 +983,9 @@ private 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) + foreach (string regexOption in regexOptionEnumValues) { System.Text.RegularExpressions.RegexOptions option = (System.Text.RegularExpressions.RegexOptions) Enum.Parse( typeof(System.Text.RegularExpressions.RegexOptions), @@ -1025,6 +1032,14 @@ private 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) { @@ -1274,7 +1289,7 @@ internal InternalParameterMetadata(Type type, bool processingDynamicParameters) /// This member is null in all cases except when constructed with using reflection /// against the Type. /// - private Type _type; + private readonly Type _type; /// /// The flags used when reflecting against the object to create the metadata. @@ -1367,7 +1382,7 @@ private void ConstructCompiledParametersUsingReflection(bool processingDynamicPa } } - private void CheckForReservedParameter(string name) + private static void CheckForReservedParameter(string name) { if (name.Equals("SelectProperty", StringComparison.OrdinalIgnoreCase) || @@ -1544,10 +1559,9 @@ private static bool IsMemberAParameter(MemberInfo member) /// The cache of the type metadata. The key for the cache is the Type.FullName. /// Note, this is a case-sensitive dictionary because Type names are case sensitive. /// - private static System.Collections.Concurrent.ConcurrentDictionary s_parameterMetadataCache = + private static readonly System.Collections.Concurrent.ConcurrentDictionary s_parameterMetadataCache = new System.Collections.Concurrent.ConcurrentDictionary(StringComparer.Ordinal); #endregion Metadata cache } } - diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index c5b889b2586..a0eead95e23 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)) { @@ -277,7 +277,7 @@ private void CheckStandardNote(TypeMemberData member, TypeData typeData, Acti } } - private bool CheckStandardPropertySet(TypeMemberData member, TypeData typeData, Action setter) + private static bool CheckStandardPropertySet(TypeMemberData member, TypeData typeData, Action setter) { var propertySet = member as PropertySetData; if (propertySet != null) @@ -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,12 +1741,11 @@ 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 Collection _errors; + private readonly Collection _errors; #region Constructors @@ -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,57 +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. - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - 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. /// @@ -1936,18 +1886,18 @@ public TypeData(Type type) : this() this.TypeName = type.FullName; } - internal bool fromTypesXmlFile { get; private set; } + internal bool fromTypesXmlFile { get; } /// /// Get the TypeName. /// - public string TypeName { get; private set; } + public string TypeName { get; } /// /// Get the members of this TypeData instance. /// The Key of the dictionary is the member's name, and the Value is a TypeMemberData instance. /// - public Dictionary Members { get; private set; } + public Dictionary Members { get; } /// /// The type converter. @@ -1966,7 +1916,7 @@ public TypeData(Type type) : this() #region StandardMember - internal Dictionary StandardMembers { get; private set; } + internal Dictionary StandardMembers { get; } /// /// The serializationMethod. @@ -2191,7 +2141,7 @@ public TypeMemberData StringSerializationSourceProperty return; } - if (!(value is NotePropertyData || value is ScriptPropertyData || value is CodePropertyData)) + if (value is not NotePropertyData && value is not ScriptPropertyData && value is not CodePropertyData) { throw PSTraceSource.NewArgumentException("value"); } @@ -2725,7 +2675,7 @@ public PropertySetData(IEnumerable referencedProperties) /// /// The referenced properties. /// - public Collection ReferencedProperties { get; private set; } + public Collection ReferencedProperties { get; } /// /// The PropertySet name. @@ -2777,7 +2727,7 @@ public MemberSetData(string name, Collection members) /// /// The members of the MemberSet. /// - public Collection Members { get; private set; } + public Collection Members { get; } /// /// Set true if the member is supposed to be hidden. @@ -3043,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 @@ -3192,7 +3142,7 @@ private static bool CheckStandardMembers(ConcurrentBag errors, string ty } while (false); - if (serializationSettingsOk == false) + if (!serializationSettingsOk) { AddError(errors, typeName, TypesXmlStrings.SerializationSettingsIgnored); members.Remove(InheritPropertySerializationSet); @@ -3291,9 +3241,14 @@ private static void AddError(ConcurrentBag errors, string typeName, stri #region add members from TypeData - private static void ProcessMembersData(ConcurrentBag errors, string typeName, IEnumerable membersData, PSMemberInfoInternalCollection membersCollection, bool isOverride) + private static void ProcessMembersData( + ConcurrentBag errors, + string typeName, + Dictionary membersData, + PSMemberInfoInternalCollection membersCollection, + bool isOverride) { - foreach (TypeMemberData typeMember in membersData) + foreach (TypeMemberData typeMember in membersData.Values) { typeMember.Process(errors, typeName, membersCollection, isOverride); } @@ -3449,12 +3404,12 @@ internal static void ProcessMemberSetData(ConcurrentBag errors, string t private static void ProcessStandardMembers( ConcurrentBag errors, string typeName, - IEnumerable standardMembers, - IEnumerable propertySets, + Dictionary standardMembers, + List propertySets, PSMemberInfoInternalCollection membersCollection, bool isOverride) { - int newMemberCount = standardMembers.Count() + propertySets.Count(); + int newMemberCount = standardMembers.Count + propertySets.Count; // If StandardMembers do not exists, we follow the original logic to create the StandardMembers if (membersCollection[PSStandardMembers] == null) @@ -3679,7 +3634,7 @@ private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeDat string typeName = typeData.TypeName; Dbg.Assert(!string.IsNullOrEmpty(typeName), "TypeData class guarantees the typeName is not null and not empty"); - var propertySets = new Collection(); + var propertySets = new List(); if (typeData.DefaultDisplayPropertySet != null) { propertySets.Add(typeData.DefaultDisplayPropertySet); @@ -3710,7 +3665,7 @@ private void ProcessTypeDataToAdd(ConcurrentBag errors, TypeData typeDat if (typeData.Members.Count > 0) { typeMembers = _extendedMembers.GetOrAdd(typeName, GetValueFactoryBasedOnInitCapacity(collectionSize)); - ProcessMembersData(errors, typeName, typeData.Members.Values, typeMembers, typeData.IsOverride); + ProcessMembersData(errors, typeName, typeData.Members, typeMembers, typeData.IsOverride); foreach (var memberName in typeData.Members.Keys) { @@ -3720,12 +3675,9 @@ 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.Values, propertySets, typeMembers, typeData.IsOverride); + ProcessStandardMembers(errors, typeName, typeData.StandardMembers, propertySets, typeMembers, typeData.IsOverride); } if (typeData.TypeConverter != null) @@ -3933,12 +3885,11 @@ 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); } - if (errors.Count > 0) + if (!errors.IsEmpty) { throw new TypeTableLoadException(errors); } @@ -3971,8 +3922,7 @@ internal Collection GetSpecificProperties(ConsolidatedString types) } PSMemberSet settings = typeMembers[PSStandardMembers] as PSMemberSet; - PSPropertySet typeProperties = settings?.Members[PropertySerializationSet] as PSPropertySet; - if (typeProperties == null) + if (settings?.Members[PropertySerializationSet] is not PSPropertySet typeProperties) { continue; } @@ -4148,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) @@ -4215,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"); @@ -4251,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) { @@ -4564,7 +4514,7 @@ internal void Initialize( /// /// Helper method to load content for a module. /// - private string GetModuleContents( + private static string GetModuleContents( string moduleName, string fileToLoad, ConcurrentBag errors, @@ -4739,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) { @@ -4817,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 aad2f70246e..26e3621c240 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) { @@ -568,9 +565,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) typeName, new PSScriptProperty( @"DisplayName", - GetScriptBlock(@"if ($this.Name.IndexOf('-') -lt 0) - { - if ($null -ne $this.ResolvedCommand) + GetScriptBlock(@"if ($null -ne $this.ResolvedCommand) { $this.Name + "" -> "" + $this.ResolvedCommand.Name } @@ -578,11 +573,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) { $this.Name + "" -> "" + $this.Definition } - } - else - { - $this.Name - }"), + "), setterScript: null, shouldCloneOnAccess: true), typeMembers, @@ -639,7 +630,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 +667,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 +754,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 +803,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 +1054,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 +1155,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 +4073,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,46 +9089,42 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag errors) #if UNIX #region UnixStat + 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); - 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); - } + 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/UserFeedbackParameters.cs b/src/System.Management.Automation/engine/UserFeedbackParameters.cs index feb1464d922..2fd26e5f32c 100644 --- a/src/System.Management.Automation/engine/UserFeedbackParameters.cs +++ b/src/System.Management.Automation/engine/UserFeedbackParameters.cs @@ -162,7 +162,7 @@ public SwitchParameter Confirm } #endregion parameters - private MshCommandRuntime _commandRuntime; + private readonly MshCommandRuntime _commandRuntime; } /// @@ -208,7 +208,6 @@ public SwitchParameter UseTransaction #endregion parameters - private MshCommandRuntime _commandRuntime; + private readonly MshCommandRuntime _commandRuntime; } } - diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index b3d62c4b06f..0de9fe0d5cc 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 @@ -29,7 +24,6 @@ using System.Threading; using Microsoft.PowerShell.Commands; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; using TypeTable = System.Management.Automation.Runspaces.TypeTable; @@ -49,7 +43,7 @@ internal static class Utils internal static bool TryCast(BigInteger value, out byte b) { - if (value < byte.MinValue || byte.MaxValue < value) + if (value < byte.MinValue || value > byte.MaxValue) { b = 0; return false; @@ -61,7 +55,7 @@ internal static bool TryCast(BigInteger value, out byte b) internal static bool TryCast(BigInteger value, out sbyte sb) { - if (value < sbyte.MinValue || sbyte.MaxValue < value) + if (value < sbyte.MinValue || value > sbyte.MaxValue) { sb = 0; return false; @@ -73,7 +67,7 @@ internal static bool TryCast(BigInteger value, out sbyte sb) internal static bool TryCast(BigInteger value, out short s) { - if (value < short.MinValue || short.MaxValue < value) + if (value < short.MinValue || value > short.MaxValue) { s = 0; return false; @@ -85,7 +79,7 @@ internal static bool TryCast(BigInteger value, out short s) internal static bool TryCast(BigInteger value, out ushort us) { - if (value < ushort.MinValue || ushort.MaxValue < value) + if (value < ushort.MinValue || value > ushort.MaxValue) { us = 0; return false; @@ -97,7 +91,7 @@ internal static bool TryCast(BigInteger value, out ushort us) internal static bool TryCast(BigInteger value, out int i) { - if (value < int.MinValue || int.MaxValue < value) + if (value < int.MinValue || value > int.MaxValue) { i = 0; return false; @@ -109,7 +103,7 @@ internal static bool TryCast(BigInteger value, out int i) internal static bool TryCast(BigInteger value, out uint u) { - if (value < uint.MinValue || uint.MaxValue < value) + if (value < uint.MinValue || value > uint.MaxValue) { u = 0; return false; @@ -121,7 +115,7 @@ internal static bool TryCast(BigInteger value, out uint u) internal static bool TryCast(BigInteger value, out long l) { - if (value < long.MinValue || long.MaxValue < value) + if (value < long.MinValue || value > long.MaxValue) { l = 0; return false; @@ -133,7 +127,7 @@ internal static bool TryCast(BigInteger value, out long l) internal static bool TryCast(BigInteger value, out ulong ul) { - if (value < ulong.MinValue || ulong.MaxValue < value) + if (value < ulong.MinValue || value > ulong.MaxValue) { ul = 0; return false; @@ -307,9 +301,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 +328,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 +347,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 +361,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 +372,6 @@ internal static void CheckSecureStringArg(SecureString arg, string argName) } } - [ArchitectureSensitive] internal static string GetStringFromSecureString(SecureString ss) { IntPtr p = IntPtr.Zero; @@ -488,9 +481,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 +576,7 @@ internal static bool IsWinPEHost() catch (ObjectDisposedException) { } finally { - if (winPEKey != null) - { - winPEKey.Dispose(); - } + winPEKey?.Dispose(); } #endif return false; @@ -645,41 +642,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 +650,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 +660,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 +1031,7 @@ internal static void EnsureModuleLoaded(string module, ExecutionContext context) finally { context.AutoLoadingModuleInProgress.Remove(module); - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } } } @@ -1129,10 +1088,7 @@ internal static List GetModules(string module, ExecutionContext co } finally { - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; @@ -1190,10 +1146,7 @@ internal static List GetModules(ModuleSpecification fullyQualified } finally { - if (ps != null) - { - ps.Dispose(); - } + ps?.Dispose(); } return result; @@ -1258,14 +1211,14 @@ internal static bool IsAdministrator() internal static bool IsReservedDeviceName(string destinationPath) { #if !UNIX - string[] reservedDeviceNames = { "CON", "PRN", "AUX", "CLOCK$", "NUL", + string[] reservedDeviceNames = { "CON", "PRN", "AUX", "CLOCK$", "NUL", "CONIN$", "CONOUT$", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" }; string compareName = Path.GetFileName(destinationPath); string noExtensionCompareName = Path.GetFileNameWithoutExtension(destinationPath); - if (((compareName.Length < 3) || (compareName.Length > 6)) && - ((noExtensionCompareName.Length < 3) || (noExtensionCompareName.Length > 6))) + if (((compareName.Length < 3) || (compareName.Length > 7)) && + ((noExtensionCompareName.Length < 3) || (noExtensionCompareName.Length > 7))) { return false; } @@ -1283,7 +1236,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 +1246,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 +1257,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"; @@ -1391,99 +1354,11 @@ internal static bool Succeeded(int hresult) return hresult >= 0; } - // Attempt to determine the existing encoding - internal static Encoding GetEncoding(string path) - { - if (!File.Exists(path)) - { - return ClrFacade.GetDefaultEncoding(); - } - - byte[] initialBytes = new byte[100]; - int bytesRead = 0; - - try - { - using (FileStream stream = System.IO.File.OpenRead(path)) - { - using (BinaryReader reader = new BinaryReader(stream)) - { - bytesRead = reader.Read(initialBytes, 0, 100); - } - } - } - catch (IOException) - { - return ClrFacade.GetDefaultEncoding(); - } - - // Test for four-byte preambles - string preamble = null; - Encoding foundEncoding = ClrFacade.GetDefaultEncoding(); - - if (bytesRead > 3) - { - preamble = string.Join("-", initialBytes[0], initialBytes[1], initialBytes[2], initialBytes[3]); - - if (encodingMap.TryGetValue(preamble, out foundEncoding)) - { - return foundEncoding; - } - } - - // Test for three-byte preambles - if (bytesRead > 2) - { - preamble = string.Join("-", initialBytes[0], initialBytes[1], initialBytes[2]); - if (encodingMap.TryGetValue(preamble, out foundEncoding)) - { - return foundEncoding; - } - } - - // Test for two-byte preambles - if (bytesRead > 1) - { - preamble = string.Join("-", initialBytes[0], initialBytes[1]); - if (encodingMap.TryGetValue(preamble, out foundEncoding)) - { - return foundEncoding; - } - } - - // Check for binary - string initialBytesAsAscii = System.Text.Encoding.ASCII.GetString(initialBytes, 0, bytesRead); - if (initialBytesAsAscii.IndexOfAny(nonPrintableCharacters) >= 0) - { - return Encoding.Unicode; - } - - return Encoding.ASCII; - } - // BigEndianUTF32 encoding is possible, but requires creation internal static readonly Encoding BigEndianUTF32Encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); // [System.Text.Encoding]::GetEncodings() | Where-Object { $_.GetEncoding().GetPreamble() } | // Add-Member ScriptProperty Preamble { $this.GetEncoding().GetPreamble() -join "-" } -PassThru | // Format-Table -Auto - internal static readonly Dictionary encodingMap = - new Dictionary() - { - { "255-254", Encoding.Unicode }, - { "254-255", Encoding.BigEndianUnicode }, - { "255-254-0-0", Encoding.UTF32 }, - { "0-0-254-255", BigEndianUTF32Encoding }, - { "239-187-191", Encoding.UTF8 }, - }; - - internal static readonly char[] nonPrintableCharacters = { - (char) 0, (char) 1, (char) 2, (char) 3, (char) 4, (char) 5, (char) 6, (char) 7, (char) 8, - (char) 11, (char) 12, (char) 14, (char) 15, (char) 16, (char) 17, (char) 18, (char) 19, (char) 20, - (char) 21, (char) 22, (char) 23, (char) 24, (char) 25, (char) 26, (char) 28, (char) 29, (char) 30, - (char) 31, (char) 127, (char) 129, (char) 141, (char) 143, (char) 144, (char) 157 }; - - internal static readonly UTF8Encoding utf8NoBom = - new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); #if !UNIX /// @@ -1532,7 +1407,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]; @@ -1559,22 +1434,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. @@ -1613,439 +1474,146 @@ 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) + /// The current ExecutionContext language mode. + internal static PSLanguageMode EnforceSystemLockDownLanguageMode(ExecutionContext context) { - "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) - { - using (var ps = System.Management.Automation.PowerShell.Create()) + switch (SystemPolicy.GetSystemLockdownPolicy()) { - ps.Runspace = runspace; - - try - { - var scriptBlock = ScriptBlock.Create(command); - var scriptBlockAst = scriptBlock.Ast as ScriptBlockAst; - if (scriptBlockAst == null) - { - 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; - } + case PSLanguageMode.FullLanguage: + context.LanguageMode = PSLanguageMode.ConstrainedLanguage; + break; - // 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.RestrictedLanguage: + context.LanguageMode = PSLanguageMode.NoLanguage; 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; - } + case PSLanguageMode.ConstrainedLanguage: + case PSLanguageMode.NoLanguage: + 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; + default: + Diagnostics.Assert(false, "Unexpected PSLanguageMode"); + context.LanguageMode = PSLanguageMode.NoLanguage; break; - } } + break; - if (success) + case SystemEnforcementMode.Audit: + switch (context.LanguageMode) { - // - // 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; + 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; } - } - 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)); - } + break; } - return false; + return context.LanguageMode; } - private static void WriteVerbose(PowerShell ps, string msg) + internal static string DisplayHumanReadableFileSize(long bytes) { - ps.Commands.Clear(); - ps.AddCommand("Write-Verbose").AddParameter("Message", msg).Invoke(); + return bytes switch + { + < 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", + }; } - private const string WhereObjectCommandAlias = "?"; - - private static bool TryGetCommandInfoList(PowerShell ps, HashSet commandNames, out Collection cmdInfoList) + /// + /// 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 (commandNames.Count == 0) + 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) { - 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; } - #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) + /// + /// Determine whether the environment variable is set and how. + /// + /// The name of the environment variable. + /// If the environment variable is not set, use this as the default value. + /// A boolean representing the value of the environment variable. + internal static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) { - if (!variableExpressionAst.VariablePath.IsAnyLocal()) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "VariableTypeNotSupported"), - variableExpressionAst); - } - - if (variableExpressionAst.VariablePath.UnqualifiedPath != "_") + var str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) { - ValidVariables.Add(variableExpressionAst.VariablePath.UnqualifiedPath); + return defaultValue; } - return AstVisitAction.Continue; - } + var boolStr = str.AsSpan(); - public override AstVisitAction VisitPipeline(PipelineAst pipelineAst) - { - if (pipelineAst.PipelineElements[0] is CommandExpressionAst) + if (boolStr.Length == 1) { - // 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. + if (boolStr[0] == '1') + { + return true; + } - // 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) + if (boolStr[0] == '0') { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "PipelineStartingWithExpressionNotSupported"), - pipelineAst); + return false; } } - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitCommand(CommandAst commandAst) - { - if (commandAst.InvocationOperator == TokenKind.Dot) + if (boolStr.Length == 3 && + (boolStr[0] == 'y' || boolStr[0] == 'Y') && + (boolStr[1] == 'e' || boolStr[1] == 'E') && + (boolStr[2] == 's' || boolStr[2] == 'S')) { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "DotSourcingNotSupported"), - commandAst); + return true; } - /* - // 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) + if (boolStr.Length == 2 && + (boolStr[0] == 'n' || boolStr[0] == 'N') && + (boolStr[1] == 'o' || boolStr[1] == 'O')) { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "CantConvertWithCommandInvocations not supported"), - commandAst); + return false; } - */ - if (commandAst.CommandElements[0] is ScriptBlockExpressionAst) + if (boolStr.Length == 4 && + (boolStr[0] == 't' || boolStr[0] == 'T') && + (boolStr[1] == 'r' || boolStr[1] == 'R') && + (boolStr[2] == 'u' || boolStr[2] == 'U') && + (boolStr[3] == 'e' || boolStr[3] == 'E')) { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "ScriptBlockInvocationNotSupported"), - commandAst); + return true; } - var commandName = commandAst.GetCommandName(); - if (commandName != null) + if (boolStr.Length == 5 && + (boolStr[0] == 'f' || boolStr[0] == 'F') && + (boolStr[1] == 'a' || boolStr[1] == 'A') && + (boolStr[2] == 'l' || boolStr[2] == 'L') && + (boolStr[3] == 's' || boolStr[3] == 'S') && + (boolStr[4] == 'e' || boolStr[4] == 'E')) { - Commands.Add(commandName); - } - - return AstVisitAction.Continue; - } - - public override AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) - { - if (redirectionAst.ToStream != RedirectionStream.Output) - { - ThrowError( - new ImplicitRemotingBatchingNotSupportedException( - "MergeRedirectionNotSupported"), - redirectionAst); + return false; } - 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; - private set; - } - - internal ImplicitRemotingBatchingNotSupportedException(string errorId) : base( - ParserStrings.ImplicitRemotingPipelineBatchingNotSupported) - { - ErrorId = errorId; + return defaultValue; } } - - #endregion } namespace System.Management.Automation.Internal @@ -2060,6 +1628,12 @@ public static class InternalTestHooks internal static bool BypassAppLockerPolicyCaching; internal static bool BypassOnlineHelpRetrieval; internal static bool ForcePromptForChoiceDefaultOption; + 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; @@ -2076,6 +1650,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; @@ -2087,27 +1666,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); } /// @@ -2238,17 +1812,14 @@ internal T Pop() /// internal sealed class ReadOnlyBag : IEnumerable { - private HashSet _hashset; + private readonly HashSet _hashset; /// /// Constructor for the readonly Hashset. /// internal ReadOnlyBag(HashSet hashset) { - if (hashset == null) - { - throw new ArgumentNullException(nameof(hashset)); - } + ArgumentNullException.ThrowIfNull(hashset); _hashset = hashset; } @@ -2278,4 +1849,18 @@ internal ReadOnlyBag(HashSet hashset) /// internal static readonly ReadOnlyBag Empty = new ReadOnlyBag(new HashSet(capacity: 0)); } + + /// + /// Helper class for simple argument validations. + /// + internal static class Requires + { + internal static void NotNullOrEmpty(ICollection value, string paramName) + { + if (value is null || value.Count == 0) + { + throw new ArgumentNullException(paramName); + } + } + } } diff --git a/src/System.Management.Automation/engine/VariableAttributeCollection.cs b/src/System.Management.Automation/engine/VariableAttributeCollection.cs index f41bb3380ba..a1a81dbb01f 100644 --- a/src/System.Management.Automation/engine/VariableAttributeCollection.cs +++ b/src/System.Management.Automation/engine/VariableAttributeCollection.cs @@ -154,8 +154,7 @@ private object VerifyNewAttribute(Attribute item) /// The variable whose value needs to be verified anytime /// the attributes change. /// - private PSVariable _variable; + private readonly PSVariable _variable; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/VariableInterfaces.cs b/src/System.Management.Automation/engine/VariableInterfaces.cs index 90411860b6d..4ced936df57 100644 --- a/src/System.Management.Automation/engine/VariableInterfaces.cs +++ b/src/System.Management.Automation/engine/VariableInterfaces.cs @@ -441,9 +441,8 @@ internal void RemoveAtScope(PSVariable variable, string scope) #region private data - private SessionStateInternal _sessionState; + private readonly SessionStateInternal _sessionState; #endregion private data } } - diff --git a/src/System.Management.Automation/engine/VariablePath.cs b/src/System.Management.Automation/engine/VariablePath.cs index 0e33ae620c5..12bfc2d03a0 100644 --- a/src/System.Management.Automation/engine/VariablePath.cs +++ b/src/System.Management.Automation/engine/VariablePath.cs @@ -222,48 +222,48 @@ internal VariablePath CloneAndSetLocal() /// /// Returns true if the path explicitly specifies 'global:'. /// - public bool IsGlobal { get { return 0 != (_flags & VariablePathFlags.Global); } } + public bool IsGlobal { get { return (_flags & VariablePathFlags.Global) != 0; } } /// /// Returns true if the path explicitly specifies 'local:'. /// - public bool IsLocal { get { return 0 != (_flags & VariablePathFlags.Local); } } + public bool IsLocal { get { return (_flags & VariablePathFlags.Local) != 0; } } /// /// Returns true if the path explicitly specifies 'private:'. /// - public bool IsPrivate { get { return 0 != (_flags & VariablePathFlags.Private); } } + public bool IsPrivate { get { return (_flags & VariablePathFlags.Private) != 0; } } /// /// Returns true if the path explicitly specifies 'script:'. /// - public bool IsScript { get { return 0 != (_flags & VariablePathFlags.Script); } } + public bool IsScript { get { return (_flags & VariablePathFlags.Script) != 0; } } /// /// Returns true if the path specifies no drive or scope qualifiers. /// - public bool IsUnqualified { get { return 0 != (_flags & VariablePathFlags.Unqualified); } } + public bool IsUnqualified { get { return (_flags & VariablePathFlags.Unqualified) != 0; } } /// /// Returns true if the path specifies a variable path with no scope qualifiers. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unscoped")] - public bool IsUnscopedVariable { get { return (0 == (_flags & VariablePathFlags.UnscopedVariableMask)); } } + public bool IsUnscopedVariable { get { return ((_flags & VariablePathFlags.UnscopedVariableMask) == 0); } } /// /// Returns true if the path defines a variable. /// - public bool IsVariable { get { return 0 != (_flags & VariablePathFlags.Variable); } } + public bool IsVariable { get { return (_flags & VariablePathFlags.Variable) != 0; } } /// /// Returns true if the path defines a function. /// - internal bool IsFunction { get { return 0 != (_flags & VariablePathFlags.Function); } } + internal bool IsFunction { get { return (_flags & VariablePathFlags.Function) != 0; } } /// /// Returns true if the path specifies a drive other than the variable drive. /// - public bool IsDriveQualified { get { return 0 != (_flags & VariablePathFlags.DriveQualified); } } + public bool IsDriveQualified { get { return (_flags & VariablePathFlags.DriveQualified) != 0; } } /// /// The drive name, or null if the path is for a variable. diff --git a/src/System.Management.Automation/engine/WinRT/IInspectable.cs b/src/System.Management.Automation/engine/WinRT/IInspectable.cs index f6f4d9ee7d9..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 { /// @@ -26,7 +27,7 @@ internal interface IInspectable { } /// /// Helper class for WinRT types. /// - internal class WinRTHelper + internal static class WinRTHelper { internal static bool IsWinRTType(Type type) { diff --git a/src/System.Management.Automation/engine/cmdlet.cs b/src/System.Management.Automation/engine/cmdlet.cs index b676337531e..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 @@ -46,11 +46,11 @@ public static HashSet CommonParameters } } - private static Lazy> s_commonParameters = new Lazy>( + private static readonly Lazy> s_commonParameters = new Lazy>( () => { return new HashSet(StringComparer.OrdinalIgnoreCase) { - "Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", + "Verbose", "Debug", "ErrorAction", "WarningAction", "InformationAction", "ProgressAction", "ErrorVariable", "WarningVariable", "OutVariable", "OutBuffer", "PipelineVariable", "InformationVariable" }; } @@ -68,7 +68,7 @@ public static HashSet OptionalCommonParameters } } - private static Lazy> s_optionalCommonParameters = new Lazy>( + private static readonly Lazy> s_optionalCommonParameters = new Lazy>( () => { return new HashSet(StringComparer.OrdinalIgnoreCase) { @@ -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,15 +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 f9f550da7fa..c4b699bd152 100644 --- a/src/System.Management.Automation/engine/debugger/Breakpoint.cs +++ b/src/System.Management.Automation/engine/debugger/Breakpoint.cs @@ -20,7 +20,7 @@ public abstract class Breakpoint /// /// The action to take when the breakpoint is hit. /// - public ScriptBlock Action { get; private set; } + public ScriptBlock Action { get; } /// /// Gets whether this breakpoint is enabled. @@ -40,7 +40,7 @@ internal void SetEnabled(bool value) /// /// This breakpoint's Id. /// - public int Id { get; private set; } + public int Id { get; } /// /// True if breakpoint is set on a script, false if the breakpoint is not scoped. @@ -53,7 +53,7 @@ internal bool IsScriptBreakpoint /// /// The script this breakpoint is on, or null if the breakpoint is not scoped. /// - public string Script { get; private set; } + public string Script { get; } #endregion properties @@ -191,9 +191,9 @@ public CommandBreakpoint(string script, WildcardPattern command, string commandS /// /// Which command this breakpoint is on. /// - public string Command { get; private set; } + public string Command { get; } - internal WildcardPattern CommandPattern { get; private set; } + internal WildcardPattern CommandPattern { get; } /// /// Gets a string representation of this breakpoint. @@ -312,12 +312,12 @@ public VariableBreakpoint(string script, string variable, VariableAccessMode acc /// /// The access mode to trigger this variable breakpoint on. /// - public VariableAccessMode AccessMode { get; private set; } + public VariableAccessMode AccessMode { get; } /// /// Which variable this breakpoint is on. /// - public string Variable { get; private set; } + public string Variable { get; } /// /// Gets the string representation of this breakpoint. @@ -415,12 +415,12 @@ public LineBreakpoint(string script, int line, int column, ScriptBlock action, i /// /// Which column this breakpoint is on. /// - public int Column { get; private set; } + public int Column { get; } /// /// Which line this breakpoint is on. /// - public int Line { get; private set; } + public int Line { get; } /// /// Gets a string representation of this breakpoint. @@ -434,10 +434,12 @@ public override string ToString() } internal int SequencePointIndex { get; set; } + internal IScriptExtent[] SequencePoints { get; set; } + internal BitArray BreakpointBitArray { get; set; } - private class CheckBreakpointInScript : AstVisitor + private sealed class CheckBreakpointInScript : AstVisitor { public static bool IsInNestedScriptBlock(Ast ast, LineBreakpoint breakpoint) { @@ -480,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; @@ -529,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; @@ -592,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 d19815ac303..985d90ab3ec 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -50,7 +50,7 @@ public enum DebuggerResumeAction /// Stop executing the script. /// Stop = 4, - }; + } /// /// Arguments for the DebuggerStop event. @@ -95,7 +95,7 @@ public DebuggerStopEventArgs( /// Note there may be more than one breakpoint on the same object (line, variable, command). A single event is /// raised for all these breakpoints. /// - public ReadOnlyCollection Breakpoints { get; private set; } + public ReadOnlyCollection Breakpoints { get; } /// /// This property must be set in the event handler to indicate the debugger what it should do next. @@ -112,7 +112,7 @@ public DebuggerStopEventArgs( /// leave pending runspace debug sessions suspended until a debugger is attached. /// internal bool SuspendRemote { get; set; } - }; + } /// /// Kinds of breakpoint updates. @@ -135,7 +135,7 @@ public enum BreakpointUpdateType /// A breakpoint was disabled. /// Disabled = 3 - }; + } /// /// Arguments for the BreakpointUpdated event. @@ -155,18 +155,18 @@ internal BreakpointUpdatedEventArgs(Breakpoint breakpoint, BreakpointUpdateType /// /// Gets the breakpoint that was updated. /// - public Breakpoint Breakpoint { get; private set; } + public Breakpoint Breakpoint { get; } /// /// Gets the type of update. /// - public BreakpointUpdateType UpdateType { get; private set; } + public BreakpointUpdateType UpdateType { get; } /// /// Gets the current breakpoint count. /// - public int BreakpointCount { get; private set; } - }; + public int BreakpointCount { get; } + } #region PSJobStartEventArgs @@ -178,29 +178,17 @@ public sealed class PSJobStartEventArgs : EventArgs /// /// Job to be started. /// - public Job Job - { - get; - private set; - } + public Job Job { get; } /// /// Job debugger. /// - public Debugger Debugger - { - get; - private set; - } + public Debugger Debugger { get; } /// /// Job is run asynchronously. /// - public bool IsAsync - { - get; - private set; - } + public bool IsAsync { get; } /// /// Constructor. @@ -226,11 +214,7 @@ public PSJobStartEventArgs(Job job, Debugger debugger, bool isAsync) public sealed class StartRunspaceDebugProcessingEventArgs : EventArgs { /// The runspace to process - public Runspace Runspace - { - get; - private set; - } + public Runspace Runspace { get; } /// /// When set to true this will cause PowerShell to process this runspace debug session through its @@ -262,11 +246,7 @@ public sealed class ProcessRunspaceDebugEndEventArgs : EventArgs /// /// The runspace where internal debug processing has ended. /// - public Runspace Runspace - { - get; - private set; - } + public Runspace Runspace { get; } /// /// Constructor. @@ -312,7 +292,7 @@ public enum DebugModes /// PowerShell remote script debugging. /// RemoteScript = 0x4 - }; + } /// /// Defines unhandled breakpoint processing behavior. @@ -810,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. @@ -991,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); @@ -1043,7 +1034,7 @@ public override bool InBreakpoint internal override bool IsPushed { - get { return (_activeDebuggers.Count > 0); } + get { return (!_activeDebuggers.IsEmpty); } } /// @@ -1077,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; } @@ -1197,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; @@ -1277,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; @@ -1333,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); } } } @@ -1365,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)) @@ -1383,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. @@ -1467,7 +1464,7 @@ private List GetVariableBreakpointsToTrigger(string variable return null; var callStackInfo = _callStack.Last(); - var currentScriptFile = (callStackInfo != null) ? callStackInfo.File : null; + var currentScriptFile = callStackInfo?.File; return breakpoints.Values.Where(bp => bp.Trigger(currentScriptFile, read: read)).ToList(); } finally @@ -1485,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; @@ -1539,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); } @@ -1573,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); + } + } } } } @@ -1595,11 +1610,14 @@ internal void OnSequencePointHit(FunctionContext functionContext) #region private members [DebuggerDisplay("{FunctionContext.CurrentPosition}")] - private class CallStackInfo + private sealed class CallStackInfo { internal InvocationInfo InvocationInfo { get; set; } + internal string File { get; set; } + internal bool DebuggerStepThrough { get; set; } + internal FunctionContext FunctionContext { get; set; } /// @@ -1609,7 +1627,7 @@ private class CallStackInfo internal bool IsFrameHidden { get; set; } internal bool TopFrameAtBreakpoint { get; set; } - }; + } private struct CallStackList { @@ -1653,7 +1671,7 @@ internal CallStackInfo Last() internal FunctionContext LastFunctionContext() { var last = Last(); - return last != null ? last.FunctionContext : null; + return last?.FunctionContext; } internal bool Any() @@ -1693,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; @@ -1710,20 +1728,20 @@ internal void Clear() // Job debugger integration. private bool _nestedDebuggerStop; - private Dictionary _runningJobs; - private ConcurrentStack _activeDebuggers; - private ConcurrentStack _debuggerStopEventArgs; + private readonly Dictionary _runningJobs; + private readonly ConcurrentStack _activeDebuggers; + private readonly ConcurrentStack _debuggerStopEventArgs; private DebuggerResumeAction _lastActiveDebuggerAction; private DebuggerResumeAction _currentDebuggerAction; private DebuggerResumeAction _previousDebuggerAction; private CallStackInfo _nestedRunningFrame; - private object _syncObject; - private object _syncActiveDebuggerStopObject; + private readonly object _syncObject; + private readonly object _syncActiveDebuggerStopObject; private int _processingOutputCount; private ManualResetEventSlim _processingOutputCompleteEvent = new ManualResetEventSlim(true); // Runspace debugger integration. - private Dictionary _runningRunspaces; + private readonly Dictionary _runningRunspaces; private const int _jobCallStackOffset = 2; private const int _runspaceCallStackOffset = 1; @@ -1732,7 +1750,7 @@ internal void Clear() private ManualResetEventSlim _preserveDebugStopEvent; // Process runspace debugger - private Lazy> _runspaceDebugQueue = new Lazy>(); + private readonly Lazy> _runspaceDebugQueue = new Lazy>(); private volatile int _processingRunspaceDebugQueue; private ManualResetEventSlim _runspaceDebugCompleteEvent; @@ -1824,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); } @@ -1965,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) @@ -2003,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(); @@ -2020,23 +2040,24 @@ private void UnbindBoundBreakpoints(List boundBreakpoints) private void SetPendingBreakpoints(FunctionContext functionContext) { - if (_pendingBreakpoints.Count == 0) - 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."); @@ -2044,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)) @@ -2055,7 +2076,15 @@ 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. @@ -2063,13 +2092,16 @@ private void SetPendingBreakpoints(FunctionContext functionContext) 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) @@ -2151,9 +2183,9 @@ 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.Count == 0 && + return _idToBreakpoint.IsEmpty && _currentDebuggerAction != DebuggerResumeAction.StepInto && _currentDebuggerAction != DebuggerResumeAction.StepOver && _currentDebuggerAction != DebuggerResumeAction.StepOut; @@ -2168,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); } } @@ -2264,7 +2293,7 @@ private void DisableDebuggerStepping() private void RestoreInternalDebugMode() { - InternalDebugMode restoreMode = ((DebugMode != DebugModes.None) && (_idToBreakpoint.Count > 0)) ? InternalDebugMode.Enabled : InternalDebugMode.Disabled; + InternalDebugMode restoreMode = ((DebugMode != DebugModes.None) && (!_idToBreakpoint.IsEmpty)) ? InternalDebugMode.Enabled : InternalDebugMode.Disabled; SetInternalDebugMode(restoreMode); } @@ -2339,8 +2368,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC // // Otherwise let root script debugger handle it. // - LocalRunspace localRunspace = _context.CurrentRunspace as LocalRunspace; - if (localRunspace == null) + if (_context.CurrentRunspace is not LocalRunspace localRunspace) { throw new PSInvalidOperationException( DebuggerStrings.CannotProcessDebuggerCommandNotStopped, @@ -2412,10 +2440,7 @@ public override void StopProcessCommand() } PowerShell ps = _psDebuggerCommand; - if (ps != null) - { - ps.BeginStop(null, null); - } + ps?.BeginStop(null, null); } /// @@ -2441,7 +2466,7 @@ public override void SetDebugMode(DebugModes mode) { SetInternalDebugMode(InternalDebugMode.Disabled); } - else if ((_idToBreakpoint.Count > 0) && (_context._debuggingMode == 0)) + else if ((!_idToBreakpoint.IsEmpty) && (_context._debuggingMode == 0)) { // Set internal debugger to active. SetInternalDebugMode(InternalDebugMode.Enabled); @@ -2548,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. @@ -2886,7 +2934,7 @@ public override Breakpoint DisableBreakpoint(Breakpoint breakpoint, int? runspac return null; } - private Debugger GetRunspaceDebugger(int runspaceId) + private static Debugger GetRunspaceDebugger(int runspaceId) { if (!Runspace.RunspaceDictionary.TryGetValue(runspaceId, out WeakReference wr)) { @@ -3266,11 +3314,7 @@ private void SetRunspaceListToStep(bool enableStepping) try { Debugger nestedDebugger = item.NestedDebugger; - - if (nestedDebugger != null) - { - nestedDebugger.SetDebuggerStepMode(enableStepping); - } + nestedDebugger?.SetDebuggerStepMode(enableStepping); } catch (PSNotImplementedException) { } } @@ -3572,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(); } @@ -3698,7 +3742,7 @@ private void RemoveFromRunningRunspaceList(Runspace runspace) } // Clean up nested debugger. - NestedRunspaceDebugger nestedDebugger = (runspaceInfo != null) ? runspaceInfo.NestedDebugger : null; + NestedRunspaceDebugger nestedDebugger = runspaceInfo?.NestedDebugger; if (nestedDebugger != null) { nestedDebugger.DebuggerStop -= HandleMonitorRunningRSDebuggerStop; @@ -3785,8 +3829,7 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent } // Get nested debugger runspace info. - NestedRunspaceDebugger nestedDebugger = senderDebugger as NestedRunspaceDebugger; - if (nestedDebugger == null) { return; } + if (senderDebugger is not NestedRunspaceDebugger nestedDebugger) { return; } PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType; @@ -3940,7 +3983,7 @@ private void DebuggerQueueThreadProc() Interlocked.CompareExchange(ref _processingRunspaceDebugQueue, 0, 1); - if (_runspaceDebugQueue.Value.Count > 0) + if (!_runspaceDebugQueue.Value.IsEmpty) { StartRunspaceForDebugQueueProcessing(); } @@ -4160,7 +4203,7 @@ internal void TraceVariableSet(string varName, object value) // because 'ToStringParser' would iterate through the enumerator to get the individual elements, which will // make irreversible changes to the enumerator. bool isValueAnIEnumerator = PSObject.Base(value) is IEnumerator; - string valAsString = isValueAnIEnumerator ? typeof(IEnumerator).Name : PSObject.ToStringParser(_context, value); + string valAsString = isValueAnIEnumerator ? nameof(IEnumerator) : PSObject.ToStringParser(_context, value); int msgLength = 60 - varName.Length; if (valAsString.Length > msgLength) @@ -4196,11 +4239,7 @@ internal abstract class NestedRunspaceDebugger : Debugger, IDisposable /// /// Type of runspace being monitored for debugging. /// - public PSMonitorRunspaceType RunspaceType - { - get; - private set; - } + public PSMonitorRunspaceType RunspaceType { get; } /// /// Unique parent debugger identifier for monitored runspace. @@ -4221,7 +4260,7 @@ public Guid ParentDebuggerId /// Runspace. /// Runspace type. /// Debugger Id of parent. - public NestedRunspaceDebugger( + protected NestedRunspaceDebugger( Runspace runspace, PSMonitorRunspaceType runspaceType, Guid parentDebuggerId) @@ -4473,14 +4512,14 @@ protected virtual DebuggerCommandResults HandlePromptCommand(PSDataCollection]: [RunspaceName]: PS C:\> - string computerName = (_runspace.ConnectionInfo != null) ? _runspace.ConnectionInfo.ComputerName : null; - string processPartPattern = "{0}[{1}:{2}]:{3}"; + string computerName = _runspace.ConnectionInfo?.ComputerName; + const string processPartPattern = "{0}[{1}:{2}]:{3}"; string processPart = StringUtil.Format(processPartPattern, @"""", DebuggerStrings.NestedRunspaceDebuggerPromptProcessName, @"$($PID)", @""""); - string locationPart = @"""PS $($executionContext.SessionState.Path.CurrentLocation)> """; + const string locationPart = @"""PS $($executionContext.SessionState.Path.CurrentLocation)> """; string promptScript = "'[DBG]: '" + " + " + processPart + " + " + "' [" + CodeGeneration.EscapeSingleQuotedStringContent(_runspace.Name) + "]: '" + " + " + locationPart; // Get the command prompt from the wrapped debugger. @@ -4528,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. /// /// @@ -4567,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(); } } @@ -4650,8 +4686,7 @@ protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs private object DrainAndBlockRemoteOutput() { // We do this only for remote runspaces. - RemoteRunspace remoteRunspace = _runspace as RemoteRunspace; - if (remoteRunspace == null) { return null; } + if (_runspace is not RemoteRunspace remoteRunspace) { return null; } var runningPowerShell = remoteRunspace.GetCurrentBasePowerShell(); if (runningPowerShell != null) @@ -4674,7 +4709,7 @@ private object DrainAndBlockRemoteOutput() return null; } - private void RestoreRemoteOutput(object runningCmd) + private static void RestoreRemoteOutput(object runningCmd) { if (runningCmd == null) { return; } @@ -4803,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. @@ -4888,19 +4923,18 @@ private InvocationInfo CreateInvocationInfoFromParent( StatementAst debugStatement = null; StatementAst callingStatement = _parentScriptBlockAst.Find( - ast => - { return ((ast is StatementAst) && (ast.Extent.StartLineNumber == callingLineNumber)); } - - , true) as StatementAst; + ast => ast is StatementAst && (ast.Extent.StartLineNumber == callingLineNumber), true) as StatementAst; if (callingStatement != null) { // Find first statement in calling statement. - StatementAst firstStatement = callingStatement.Find(ast => { return ((ast is StatementAst) && ast.Extent.StartLineNumber > callingLineNumber); }, true) as StatementAst; + StatementAst firstStatement = callingStatement.Find( + ast => ast is StatementAst && ast.Extent.StartLineNumber > callingLineNumber, true) as StatementAst; if (firstStatement != null) { int adjustedLineNumber = firstStatement.Extent.StartLineNumber + debugLineNumber - 1; - debugStatement = callingStatement.Find(ast => { return ((ast is StatementAst) && ast.Extent.StartLineNumber == adjustedLineNumber); }, true) as StatementAst; + debugStatement = callingStatement.Find( + ast => ast is StatementAst && ast.Extent.StartLineNumber == adjustedLineNumber, true) as StatementAst; } } @@ -4933,7 +4967,7 @@ private InvocationInfo CreateInvocationInfoFromParent( return null; } - private string FixUpStatementExtent(int startColNum, string stateExtentText) + private static string FixUpStatementExtent(int startColNum, string stateExtentText) { Text.StringBuilder sb = new Text.StringBuilder(); sb.Append(' ', startColNum); @@ -4945,7 +4979,7 @@ private string FixUpStatementExtent(int startColNum, string stateExtentText) private object DrainAndBlockRemoteOutput() { // We only do this for remote runspaces. - if (!(_runspace is RemoteRunspace)) { return null; } + if (_runspace is not RemoteRunspace) { return null; } try { @@ -4972,7 +5006,7 @@ private object DrainAndBlockRemoteOutput() return null; } - private void RestoreRemoteOutput(object runningCmd) + private static void RestoreRemoteOutput(object runningCmd) { if (runningCmd == null) { return; } @@ -4984,10 +5018,7 @@ private void RestoreRemoteOutput(object runningCmd) else { Pipeline pipelineCommand = runningCmd as Pipeline; - if (pipelineCommand != null) - { - pipelineCommand.ResumeIncomingData(); - } + pipelineCommand?.ResumeIncomingData(); } } @@ -5018,11 +5049,7 @@ public DebuggerResumeAction? ResumeAction /// True if debugger evaluated command. Otherwise evaluation was /// performed by PowerShell. /// - public bool EvaluatedByDebugger - { - get; - private set; - } + public bool EvaluatedByDebugger { get; } #endregion @@ -5080,13 +5107,13 @@ internal class DebuggerCommandProcessor private const int DefaultListLineCount = 16; // table of debugger commands - private Dictionary _commandTable; + private readonly Dictionary _commandTable; // the Help command - private DebuggerCommand _helpCommand; + private readonly DebuggerCommand _helpCommand; // the List command - private DebuggerCommand _listCommand; + private readonly DebuggerCommand _listCommand; // last command processed private DebuggerCommand _lastCommand; @@ -5236,7 +5263,7 @@ private DebuggerCommand DoProcessCommand(PSHost host, string command, Invocation /// /// Displays the help text for the debugger commands. /// - private void DisplayHelp(PSHost host, IList output) + private static void DisplayHelp(PSHost host, IList output) { WriteLine(string.Empty, host, output); WriteLine(StringUtil.Format(DebuggerStrings.StepHelp, StepShortcut, StepCommand), host, output); @@ -5351,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); @@ -5364,49 +5390,31 @@ private void DisplayScript(PSHost host, IList output, InvocationInfo i WriteCR(host, output); } - private void WriteLine(string line, PSHost host, IList output) + 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 void WriteCR(PSHost host, IList output) + 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 void WriteErrorLine(string error, PSHost host, IList output) + 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))); } } @@ -5488,18 +5496,18 @@ public PSDebugContext(InvocationInfo invocationInfo, List breakpoint /// /// InvocationInfo of the command currently being executed. /// - public InvocationInfo InvocationInfo { get; private set; } + public InvocationInfo InvocationInfo { get; } /// /// If not empty, indicates that the execution was suspended because one or more breakpoints /// were hit. Otherwise, the execution was suspended as part of a step operation. /// - public Breakpoint[] Breakpoints { get; private set; } + public Breakpoint[] Breakpoints { get; } /// /// Gets the object that triggered the current dynamic breakpoint. /// - public object Trigger { get; private set; } + public object Trigger { get; } } #endregion @@ -5567,13 +5575,13 @@ public int ScriptLineNumber /// /// The InvocationInfo of the command. /// - public InvocationInfo InvocationInfo { get; private set; } + public InvocationInfo InvocationInfo { get; } /// /// The position information for the current position in the frame. Null if the frame is not /// associated with a script. /// - public IScriptExtent Position { get; private set; } + public IScriptExtent Position { get; } /// /// The name of the function associated with this frame. @@ -5666,7 +5674,7 @@ namespace System.Management.Automation.Internal [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes", Justification = "Needed Internal use only")] public static class DebuggerUtils { - private static SortedSet s_noHistoryCommandNames = new SortedSet(StringComparer.OrdinalIgnoreCase) + private static readonly SortedSet s_noHistoryCommandNames = new SortedSet(StringComparer.OrdinalIgnoreCase) { "prompt", "Set-PSDebuggerAction", @@ -5715,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. @@ -5765,12 +5773,12 @@ public abstract class PSMonitorRunspaceInfo /// /// Created Runspace. /// - public Runspace Runspace { get; private set; } + public Runspace Runspace { get; } /// /// Type of runspace for monitoring. /// - public PSMonitorRunspaceType RunspaceType { get; private set; } + public PSMonitorRunspaceType RunspaceType { get; } /// /// Nested debugger wrapper for runspace debugger. @@ -5876,7 +5884,7 @@ public sealed class PSEmbeddedMonitorRunspaceInfo : PSMonitorRunspaceInfo /// /// PowerShell command to run. Can be null. /// - public PowerShell Command { get; private set; } + public PowerShell Command { get; } /// /// Unique parent debugger identifier. diff --git a/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs b/src/System.Management.Automation/engine/hostifaces/AsyncResult.cs index 8a0101ae802..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). @@ -42,7 +42,7 @@ internal class AsyncResult : IAsyncResult /// internal AsyncResult(Guid ownerId, AsyncCallback callback, object state) { - Dbg.Assert(Guid.Empty != ownerId, "ownerId cannot be empty"); + Dbg.Assert(ownerId != Guid.Empty, "ownerId cannot be empty"); OwnerId = ownerId; Callback = callback; AsyncState = state; @@ -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) { @@ -152,10 +149,7 @@ internal void SetAsCompleted(Exception exception) } // call the user supplied callback - if (Callback != null) - { - Callback(this); - } + Callback?.Invoke(this); } /// @@ -181,10 +175,7 @@ internal void SignalWaitHandle() { lock (SyncObject) { - if (_completedWaitHandle != null) - { - _completedWaitHandle.Set(); - } + _completedWaitHandle?.Set(); } } @@ -225,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 9fca84d4a33..0cac1d02b6f 100644 --- a/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/ChoiceDescription.cs @@ -6,10 +6,9 @@ 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 { @@ -29,7 +28,6 @@ class ChoiceDescription /// /// is null or empty. /// - public ChoiceDescription(string label) { @@ -59,7 +57,6 @@ class ChoiceDescription /// /// is null. /// - public ChoiceDescription(string label, string helpMessage) { @@ -87,12 +84,11 @@ 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. /// - public string Label @@ -115,7 +111,6 @@ class ChoiceDescription /// This should be a few sentences to describe the field, suitable for presentation as a tool tip. /// Avoid placing including formatting characters such as newline and tab. /// - public string HelpMessage @@ -139,4 +134,3 @@ class ChoiceDescription } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/Command.cs b/src/System.Management.Automation/engine/hostifaces/Command.cs index b98e447563b..3de339ff112 100644 --- a/src/System.Management.Automation/engine/hostifaces/Command.cs +++ b/src/System.Management.Automation/engine/hostifaces/Command.cs @@ -422,7 +422,7 @@ public void MergeMyResults(PipelineResultTypes myResult, PipelineResultTypes toR } } - private Pipe GetRedirectionPipe( + private static Pipe GetRedirectionPipe( PipelineResultTypes toType, MshCommandRuntime mcr) { @@ -568,7 +568,7 @@ CommandOrigin origin /// property is bool, not bool? (from V1), so it should probably /// be deprecated, at least for internal use. /// - private bool? _useLocalScope; + private readonly bool? _useLocalScope; #endregion Private fields @@ -662,7 +662,7 @@ internal PSObject ToPSObjectForRemoting(Version psRPVersion) commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeUnclaimedPreviousCommandResults, this.MergeUnclaimedPreviousCommandResults)); if (psRPVersion != null && - psRPVersion >= RemotingConstants.ProtocolVersionWin10RTM) + psRPVersion >= RemotingConstants.ProtocolVersion_2_3) { // V5 merge instructions commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeError, MergeInstructions[(int)MergeType.Error])); @@ -672,7 +672,7 @@ internal PSObject ToPSObjectForRemoting(Version psRPVersion) commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeInformation, MergeInstructions[(int)MergeType.Information])); } else if (psRPVersion != null && - psRPVersion >= RemotingConstants.ProtocolVersionWin8RTM) + psRPVersion >= RemotingConstants.ProtocolVersion_2_2) { // V3 merge instructions. commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeError, MergeInstructions[(int)MergeType.Error])); @@ -900,4 +900,3 @@ internal string GetCommandStringForHistory() } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index a57b2d4a626..ccb918c7dd9 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,14 +219,11 @@ 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 - }; + } /// /// Defines type which has information about RunspaceState and @@ -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 @@ -450,9 +452,9 @@ public abstract class Runspace : IDisposable #region Private Data private static int s_globalId; - private Stack _runningPowerShells; + private readonly Stack _runningPowerShells; private PowerShell _baseRunningPowerShell; - private object _syncObject; + private readonly object _syncObject; #endregion @@ -574,7 +576,7 @@ public static bool CanUseDefaultRunspace { return (localPipeline.NestedPipelineExecutionThread.ManagedThreadId - == Threading.Thread.CurrentThread.ManagedThreadId); + == Environment.CurrentManagedThreadId); } } @@ -650,7 +652,7 @@ public bool RunspaceIsRemote { get { - return !(this is LocalRunspace || ConnectionInfo == null); + return this is not LocalRunspace && ConnectionInfo != null; } } @@ -698,7 +700,7 @@ public Guid InstanceId /// /// Runspace is not opened. /// - internal System.Management.Automation.ExecutionContext ExecutionContext + internal ExecutionContext ExecutionContext { get { @@ -758,11 +760,13 @@ public string Name /// /// Gets the Runspace Id. /// - public int Id - { - get; - private set; - } + public int Id { get; } + + /// + /// Gets and sets a boolean indicating whether the runspace has a + /// debugger attached with Debug-Runspace. + /// + public bool IsRemoteDebuggerAttached { get; internal set; } /// /// Returns protocol version that the remote server uses for PS remoting. @@ -805,8 +809,8 @@ internal static ReadOnlyDictionary> RunspaceDiction } } - private static SortedDictionary> s_runspaceDictionary; - private static object s_syncObject; + private static readonly SortedDictionary> s_runspaceDictionary; + private static readonly object s_syncObject; /// /// Returns a read only list of runspaces. @@ -947,7 +951,8 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: - if (this.InNestedPrompt || !(this is RemoteRunspace) && this.Debugger.InBreakpoint) + if (this.InNestedPrompt + || (this is not RemoteRunspace && this.Debugger.InBreakpoint)) { this.RunspaceAvailability = RunspaceAvailability.AvailableForNestedCommand; } @@ -955,7 +960,7 @@ internal void UpdateRunspaceAvailability(PipelineState pipelineState, bool raise { RemoteRunspace remoteRunspace = this as RemoteRunspace; RemoteDebugger remoteDebugger = (remoteRunspace != null) ? remoteRunspace.Debugger as RemoteDebugger : null; - Internal.ConnectCommandInfo remoteCommand = (remoteRunspace != null) ? remoteRunspace.RemoteCommand : null; + Internal.ConnectCommandInfo remoteCommand = remoteRunspace?.RemoteCommand; if (((pipelineState == PipelineState.Completed) || (pipelineState == PipelineState.Failed) || ((pipelineState == PipelineState.Stopped) && (this.RunspaceStateInfo.State == RunspaceState.Opened))) && (remoteCommand != null) && (cmdInstanceId != null) && (remoteCommand.CommandId == cmdInstanceId)) @@ -1511,7 +1516,10 @@ internal PowerShell PopRunningPowerShell() if (count > 0) { - if (count == 1) { _baseRunningPowerShell = null; } + if (count == 1) + { + _baseRunningPowerShell = null; + } return _runningPowerShells.Pop(); } @@ -1572,7 +1580,7 @@ protected virtual void Dispose(bool disposing) /// /// Gets the execution context. /// - internal abstract System.Management.Automation.ExecutionContext GetExecutionContext + internal abstract ExecutionContext GetExecutionContext { get; } @@ -1593,7 +1601,7 @@ public virtual Debugger Debugger get { var context = GetExecutionContext; - return (context != null) ? context.Debugger : null; + return context?.Debugger; } } @@ -1618,8 +1626,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); @@ -1628,8 +1636,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); @@ -1654,6 +1662,7 @@ public virtual void ResetRunspaceState() // Used for pipeline id generation. private long _pipelineIdSeed; + // Generate pipeline id unique to this runspace internal long GeneratePipelineId() { @@ -1671,7 +1680,7 @@ internal SessionStateProxy() { } - private RunspaceBase _runspace; + private readonly RunspaceBase _runspace; internal SessionStateProxy(RunspaceBase runspace) { diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionBase.cs index cffbc6d81a1..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,15 +286,18 @@ 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 // on the host calling ReportStartupTelemetry. - if (!(this.Host is IHostProvidesTelemetryData)) + if (this.Host is not IHostProvidesTelemetryData) { TelemetryAPI.ReportStartupTelemetry(null); } @@ -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) { @@ -829,7 +858,7 @@ internal void AddToRunningPipelineList(PipelineBase pipeline) lock (_pipelineListLock) { - if (ByPassRunspaceStateCheck == false && RunspaceState != RunspaceState.Opened) + if (!ByPassRunspaceStateCheck && RunspaceState != RunspaceState.Opened) { InvalidRunspaceStateException e = new InvalidRunspaceStateException @@ -924,12 +953,13 @@ internal bool WaitForFinishofPipelines() Tuple stateInfo = new Tuple(waitHandles, waitAllIsDone); ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object state) - { - var tuple = (Tuple)state; - WaitHandle.WaitAll(tuple.Item1); - tuple.Item2.Set(); - }), stateInfo); + (object state) => + { + var tuple = (Tuple)state; + WaitHandle.WaitAll(tuple.Item1); + tuple.Item2.Set(); + }), + stateInfo); return waitAllIsDone.WaitOne(); } } @@ -1002,7 +1032,7 @@ internal void StopNestedPipelines(Pipeline pipeline) // first check if this pipeline is in the list of running // pipelines. It is possible that pipeline has already // completed. - if (RunningPipelines.Contains(pipeline) == false) + if (!RunningPipelines.Contains(pipeline)) { return; } @@ -1573,7 +1603,7 @@ internal ProviderIntrinsics InvokeProvider /// internal override SessionStateProxy GetSessionStateProxy() { - return _sessionStateProxy ?? (_sessionStateProxy = new SessionStateProxy(this)); + return _sessionStateProxy ??= new SessionStateProxy(this); } #endregion session state proxy diff --git a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs index 2021ae6abf9..45591ee2906 100644 --- a/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs +++ b/src/System.Management.Automation/engine/hostifaces/ConnectionFactory.cs @@ -454,11 +454,11 @@ public static RunspacePool CreateRunspacePool(int minRunspaces, public static RunspacePool CreateRunspacePool(int minRunspaces, int maxRunspaces, RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable, PSPrimitiveDictionary applicationArguments) { - if ((!(connectionInfo is WSManConnectionInfo)) && - (!(connectionInfo is NewProcessConnectionInfo)) && - (!(connectionInfo is NamedPipeConnectionInfo)) && - (!(connectionInfo is VMConnectionInfo)) && - (!(connectionInfo is ContainerConnectionInfo))) + if (connectionInfo is not WSManConnectionInfo && + connectionInfo is not NewProcessConnectionInfo && + connectionInfo is not NamedPipeConnectionInfo && + connectionInfo is not VMConnectionInfo && + connectionInfo is not ContainerConnectionInfo) { throw new NotSupportedException(); } @@ -476,58 +476,64 @@ public static RunspacePool CreateRunspacePool(int minRunspaces, #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 WSManConnectionInfo)) && - (!(connectionInfo is NewProcessConnectionInfo)) && - (!(connectionInfo is NamedPipeConnectionInfo)) && - (!(connectionInfo is SSHConnectionInfo)) && - (!(connectionInfo is VMConnectionInfo)) && - (!(connectionInfo is ContainerConnectionInfo))) - { - throw new NotSupportedException(); - } - if (connectionInfo is WSManConnectionInfo) { RemotingCommandUtil.CheckHostRemotingPrerequisites(); @@ -558,19 +554,21 @@ public static Runspace CreateRunspace(RunspaceConnectionInfo connectionInfo, PSH } /// + /// 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 }; @@ -606,4 +626,3 @@ public static Runspace CreateOutOfProcessRunspace(TypeTable typeTable, PowerShel #endregion V3 Extensions } } - diff --git a/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs b/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs index 1542ab865f7..56d4225be56 100644 --- a/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/DefaultHost.cs @@ -14,7 +14,6 @@ namespace Microsoft.PowerShell /// This is the default host implementing PSHost offering minimal host capabilities. /// Runspace is the primary user of this class. /// - internal class DefaultHost : PSHost { #region ctor @@ -25,7 +24,6 @@ internal class DefaultHost : PSHost /// Current culture for this host. /// Current UI culture for this host. /// - internal DefaultHost(CultureInfo currentCulture, CultureInfo currentUICulture) { CurrentCulture = currentCulture; @@ -70,7 +68,6 @@ internal DefaultHost(CultureInfo currentCulture, CultureInfo currentUICulture) /// /// /// - public override void SetShouldExit(int exitCode) @@ -85,7 +82,6 @@ public override /// /// On calling this method /// - public override void EnterNestedPrompt() @@ -112,7 +108,6 @@ public override /// /// /// - public override void NotifyBeginApplication() @@ -125,7 +120,6 @@ public override /// /// /// - public override void NotifyEndApplication() @@ -139,4 +133,3 @@ public override #endregion private fields } } - diff --git a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs index 545440a1574..3f47fedb55e 100644 --- a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs @@ -16,13 +16,12 @@ namespace System.Management.Automation.Host { /// /// Provides a description of a field for use by . - /// + /// /// /// /// It is permitted to subclass /// but there is no established scenario for doing this, nor has it been tested. /// - public class FieldDescription { @@ -35,7 +34,6 @@ public class /// /// is null or empty. /// - public FieldDescription(string name) { @@ -69,7 +67,6 @@ public string Name /// /// If is null. /// - public void SetParameterType(System.Type parameterType) @@ -92,11 +89,10 @@ public string Name /// /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// /// - public string ParameterTypeName @@ -119,11 +115,10 @@ public string Name /// /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// /// - public string ParameterTypeFullName @@ -149,9 +144,8 @@ public string Name /// to load the containing assembly to access the type information. AssemblyName is used for this purpose. /// /// If not already set by a call to , - /// will be used as the type. + /// will be used as the type. /// - public string ParameterAssemblyFullName @@ -171,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. /// /// @@ -180,16 +174,15 @@ 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. /// /// If no label is set, then the empty string is returned. /// - public string Label @@ -222,7 +215,6 @@ public string Name /// This should be a few sentences to describe the field, suitable for presentation as a tool tip. /// Avoid placing including formatting characters such as newline and tab. /// - public string HelpMessage @@ -248,7 +240,6 @@ public string Name /// /// Gets and sets whether a value must be supplied for this field. /// - public bool IsMandatory @@ -265,16 +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 @@ -293,16 +283,15 @@ 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 Collection Attributes { - get { return metadata ?? (metadata = new Collection()); } + get { return metadata ??= new Collection(); } } /// @@ -312,7 +301,6 @@ public string Name /// /// If is null. /// - internal void SetParameterTypeName(string nameOfType) @@ -332,7 +320,6 @@ public string Name /// /// If is null. /// - internal void SetParameterTypeFullName(string fullNameOfType) @@ -352,7 +339,6 @@ public string Name /// /// If is null. /// - internal void SetParameterAssemblyFullName(string fullNameOfAssembly) @@ -428,4 +414,3 @@ internal bool IsFromRemoteHost #endregion } } - diff --git a/src/System.Management.Automation/engine/hostifaces/History.cs b/src/System.Management.Automation/engine/hostifaces/History.cs index 7bedae6c737..00a090d4b22 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; @@ -109,7 +110,6 @@ public override string ToString() /// /// Cleared status of an entry. /// - internal bool Cleared { get; set; } = false; /// @@ -139,7 +139,7 @@ public override string ToString() /// /// Id of the pipeline corresponding to this history entry. /// - private long _pipelineId; + private readonly long _pipelineId; /// /// Returns a clone of this object. @@ -167,7 +167,6 @@ internal class History /// /// Constructs history store. /// - internal History(ExecutionContext context) { // Create history size variable. Add ValidateRangeAttribute to @@ -264,7 +263,7 @@ internal HistoryInfo GetEntry(long id) HistoryInfo entry = CoreGetEntry(id); if (entry != null) - if (entry.Cleared == false) + if (!entry.Cleared) return entry.Clone(); return null; @@ -418,7 +417,7 @@ internal HistoryInfo[] GetEntries(long id, long count, SwitchParameter newest) } else { - index = _countEntriesAdded;//SmallestIDinBuffer + index = _countEntriesAdded; //SmallestIDinBuffer for (long i = count - 1; i >= 0;) { @@ -498,7 +497,7 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S for (long i = 0; i <= count - 1;) { if (id > _countEntriesAdded) break; - if (_buffer[GetIndexFromId(id)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) + if (!_buffer[GetIndexFromId(id)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++; } @@ -511,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) @@ -522,7 +521,7 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S } if (id < 1) break; - if (_buffer[GetIndexFromId(id)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) + if (!_buffer[GetIndexFromId(id)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(id)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(id)].Clone()); i++; } @@ -535,7 +534,7 @@ internal HistoryInfo[] GetEntries(WildcardPattern wildcardpattern, long count, S { for (long i = 1; i <= _countEntriesAdded; i++) { - if (_buffer[GetIndexFromId(i)].Cleared == false && wildcardpattern.IsMatch(_buffer[GetIndexFromId(i)].CommandLine.Trim())) + if (!_buffer[GetIndexFromId(i)].Cleared && wildcardpattern.IsMatch(_buffer[GetIndexFromId(i)].CommandLine.Trim())) { cmdlist.Add(_buffer[GetIndexFromId(i)].Clone()); } @@ -582,11 +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; @@ -604,7 +602,6 @@ internal int Buffercapacity() /// Returns id for the entry. This id should be used to fetch /// the entry from the buffer. /// Id starts from 1 and is incremented by 1 for each new entry - private long Add(HistoryInfo entry) { if (entry == null) @@ -664,7 +661,7 @@ private long SmallestIDinBuffer() for (int i = 0; i < _buffer.Length; i++) { // assign the first entry in the buffer as min. - if (_buffer[i] != null && _buffer[i].Cleared == false) + if (_buffer[i] != null && !_buffer[i].Cleared) { minID = _buffer[i].Id; break; @@ -673,7 +670,7 @@ private long SmallestIDinBuffer() // check for the minimum id that is not cleared for (int i = 0; i < _buffer.Length; i++) { - if (_buffer[i] != null && _buffer[i].Cleared == false) + if (_buffer[i] != null && !_buffer[i].Cleared) if (minID > _buffer[i].Id) minID = _buffer[i].Id; } @@ -759,11 +756,11 @@ private void IncrementCountOfEntriesInBuffer() /// Get the current history size. /// /// - private int GetHistorySize() + private static int GetHistorySize() { int historySize = 0; var executionContext = LocalPipeline.GetExecutionContextFromTLS(); - object obj = (executionContext != null) ? executionContext.GetVariableValue(SpecialVariables.HistorySizeVarPath) : null; + object obj = executionContext?.GetVariableValue(SpecialVariables.HistorySizeVarPath); if (obj != null) { try @@ -807,7 +804,7 @@ private int GetHistorySize() /// /// Private object for synchronization. /// - private object _syncRoot = new object(); + private readonly object _syncRoot = new object(); #endregion private @@ -818,6 +815,31 @@ internal long GetNextHistoryId() { return _countEntriesAdded + 1; } + + #region invoke_loop_detection + + /// + /// This is a set of HistoryInfo ids which are currently being executed in the + /// pipelines of the Runspace that is holding this 'History' instance. + /// + private readonly HashSet _invokeHistoryIds = new HashSet(); + + internal bool PresentInInvokeHistoryEntrySet(HistoryInfo entry) + { + return _invokeHistoryIds.Contains(entry.Id); + } + + internal void AddToInvokeHistoryEntrySet(HistoryInfo entry) + { + _invokeHistoryIds.Add(entry.Id); + } + + internal void RemoveFromInvokeHistoryEntrySet(HistoryInfo entry) + { + _invokeHistoryIds.Remove(entry.Id); + } + + #endregion invoke_loop_detection } /// @@ -837,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 @@ -865,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 @@ -1021,68 +1043,45 @@ protected override void EndProcessing() // ids were provided, throw exception if (_multipleIdProvided) { - Exception ex = - new ArgumentException - ( - StringUtil.Format(HistoryStrings.InvokeHistoryMultipleCommandsError) - ); - - ThrowTerminatingError - ( - new ErrorRecord - ( - ex, + ThrowTerminatingError( + new ErrorRecord( + new ArgumentException(HistoryStrings.InvokeHistoryMultipleCommandsError), "InvokeHistoryMultipleCommandsError", ErrorCategory.InvalidArgument, - null - ) - ); + targetObject: null)); } - History history = ((LocalRunspace)Context.CurrentRunspace).History; + var ctxRunspace = (LocalRunspace)Context.CurrentRunspace; + History history = ctxRunspace.History; Dbg.Assert(history != null, "History should be non null"); // Get the history entry to invoke HistoryInfo entry = GetHistoryEntryToInvoke(history); + string commandToInvoke = entry.CommandLine; - // Check if there is a loop in invoke-history - LocalPipeline pipeline = (LocalPipeline)((LocalRunspace)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); - - if (pipeline.PresentInInvokeHistoryEntryList(entry) == false) + if (!ShouldProcess(commandToInvoke)) { - pipeline.AddToInvokeHistoryEntryList(entry); + return; } - else - { - Exception ex = - new InvalidOperationException - ( - StringUtil.Format(HistoryStrings.InvokeHistoryLoopDetected) - ); - ThrowTerminatingError - ( - new ErrorRecord - ( - ex, + // Check if there is a loop in invoke-history + if (history.PresentInInvokeHistoryEntrySet(entry)) + { + ThrowTerminatingError( + new ErrorRecord( + new InvalidOperationException(HistoryStrings.InvokeHistoryLoopDetected), "InvokeHistoryLoopDetected", ErrorCategory.InvalidOperation, - null - ) - ); + targetObject: null)); } - - // Replace Invoke-History with string which is getting invoked - ReplaceHistoryString(entry); - - // Now invoke the command - string commandToInvoke = entry.CommandLine; - - if (ShouldProcess(commandToInvoke) == false) + else { - return; + history.AddToInvokeHistoryEntrySet(entry); } + // Replace Invoke-History with string which is getting invoked + ReplaceHistoryString(entry, ctxRunspace); + try { // Echo command @@ -1100,12 +1099,12 @@ protected override void EndProcessing() { ps.AddScript(commandToInvoke); - EventHandler debugAdded = delegate (object sender, DataAddedEventArgs e) { DebugRecord record = (DebugRecord)((PSDataCollection)sender)[e.Index]; WriteDebug(record.Message); }; - EventHandler errorAdded = delegate (object sender, DataAddedEventArgs e) { ErrorRecord record = (ErrorRecord)((PSDataCollection)sender)[e.Index]; WriteError(record); }; - EventHandler informationAdded = delegate (object sender, DataAddedEventArgs e) { InformationRecord record = (InformationRecord)((PSDataCollection)sender)[e.Index]; WriteInformation(record); }; - EventHandler progressAdded = delegate (object sender, DataAddedEventArgs e) { ProgressRecord record = (ProgressRecord)((PSDataCollection)sender)[e.Index]; WriteProgress(record); }; - EventHandler verboseAdded = delegate (object sender, DataAddedEventArgs e) { VerboseRecord record = (VerboseRecord)((PSDataCollection)sender)[e.Index]; WriteVerbose(record.Message); }; - EventHandler warningAdded = delegate (object sender, DataAddedEventArgs e) { WarningRecord record = (WarningRecord)((PSDataCollection)sender)[e.Index]; WriteWarning(record.Message); }; + EventHandler debugAdded = (object sender, DataAddedEventArgs e) => { DebugRecord record = (DebugRecord)((PSDataCollection)sender)[e.Index]; WriteDebug(record.Message); }; + EventHandler errorAdded = (object sender, DataAddedEventArgs e) => { ErrorRecord record = (ErrorRecord)((PSDataCollection)sender)[e.Index]; WriteError(record); }; + EventHandler informationAdded = (object sender, DataAddedEventArgs e) => { InformationRecord record = (InformationRecord)((PSDataCollection)sender)[e.Index]; WriteInformation(record); }; + EventHandler progressAdded = (object sender, DataAddedEventArgs e) => { ProgressRecord record = (ProgressRecord)((PSDataCollection)sender)[e.Index]; WriteProgress(record); }; + EventHandler verboseAdded = (object sender, DataAddedEventArgs e) => { VerboseRecord record = (VerboseRecord)((PSDataCollection)sender)[e.Index]; WriteVerbose(record.Message); }; + EventHandler warningAdded = (object sender, DataAddedEventArgs e) => { WarningRecord record = (WarningRecord)((PSDataCollection)sender)[e.Index]; WriteWarning(record.Message); }; ps.Streams.Debug.DataAdded += debugAdded; ps.Streams.Error.DataAdded += errorAdded; @@ -1132,11 +1131,11 @@ protected override void EndProcessing() { WriteObject(results, true); } - - pipeline.RemoveFromInvokeHistoryEntryList(entry); } finally { + history.RemoveFromInvokeHistoryEntrySet(entry); + if (localRunspace != null) { localRunspace.InInternalNestedPrompt = false; @@ -1317,10 +1316,9 @@ private void PopulateIdAndCommandLine() /// in the pipeline. If there are more than one element in pipeline /// (ex A | Invoke-History 2 | B) then we cannot do this replacement. /// - private void ReplaceHistoryString(HistoryInfo entry) + private static void ReplaceHistoryString(HistoryInfo entry, LocalRunspace localRunspace) { - // Get the current pipeline - LocalPipeline pipeline = (LocalPipeline)((LocalRunspace)Context.CurrentRunspace).GetCurrentlyRunningPipeline(); + var pipeline = (LocalPipeline)localRunspace.GetCurrentlyRunningPipeline(); if (pipeline.AddToHistory) { pipeline.HistoryString = entry.CommandLine; @@ -1341,14 +1339,14 @@ public class AddHistoryCommand : PSCmdlet /// This parameter specifies the current pipeline object. /// [Parameter(Position = 0, ValueFromPipeline = true)] - public PSObject[] InputObject { set; get; } + public PSObject[] InputObject { get; set; } private bool _passthru; /// /// 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; } @@ -1416,7 +1414,7 @@ void ProcessRecord() } /// - /// Convert mshObject that has has the properties of an HistoryInfo + /// Convert mshObject that has the properties of an HistoryInfo /// object in to HistoryInfo object. /// /// @@ -1436,121 +1434,44 @@ void ProcessRecord() { break; } + // Read CommandLine property - string commandLine = GetPropertyValue(mshObject, "CommandLine") as string; - if (commandLine == null) + if (GetPropertyValue(mshObject, "CommandLine") is not string commandLine) { break; } // Read ExecutionStatus property object pipelineState = GetPropertyValue(mshObject, "ExecutionStatus"); - if (pipelineState == null) - { - break; - } - - PipelineState executionStatus; - if (pipelineState is PipelineState) - { - executionStatus = (PipelineState)pipelineState; - } - else if (pipelineState is PSObject) - { - PSObject serializedPipelineState = pipelineState as PSObject; - object baseObject = serializedPipelineState.BaseObject; - if (!(baseObject is int)) - { - break; - } - - executionStatus = (PipelineState)baseObject; - if (executionStatus < PipelineState.NotStarted || executionStatus > PipelineState.Failed) - { - break; - } - } - else if (pipelineState is string) - { - try - { - executionStatus = (PipelineState)Enum.Parse(typeof(PipelineState), (string)pipelineState); - } - catch (ArgumentException) - { - break; - } - } - else + if (pipelineState == null || !LanguagePrimitives.TryConvertTo(pipelineState, out PipelineState executionStatus)) { break; } // Read StartExecutionTime property - DateTime startExecutionTime; object temp = GetPropertyValue(mshObject, "StartExecutionTime"); - if (temp == null) - { - break; - } - else if (temp is DateTime) - { - startExecutionTime = (DateTime)temp; - } - else if (temp is string) - { - try - { - startExecutionTime = DateTime.Parse((string)temp, System.Globalization.CultureInfo.CurrentCulture); - } - catch (FormatException) - { - break; - } - } - else + if (temp == null || !LanguagePrimitives.TryConvertTo(temp, CultureInfo.CurrentCulture, out DateTime startExecutionTime)) { break; } // Read EndExecutionTime property - DateTime endExecutionTime; temp = GetPropertyValue(mshObject, "EndExecutionTime"); - if (temp == null) - { - break; - } - else if (temp is DateTime) - { - endExecutionTime = (DateTime)temp; - } - else if (temp is string) - { - try - { - endExecutionTime = DateTime.Parse((string)temp, System.Globalization.CultureInfo.CurrentCulture); - } - catch (FormatException) - { - break; - } - } - else + if (temp == null || !LanguagePrimitives.TryConvertTo(temp, CultureInfo.CurrentCulture, out DateTime endExecutionTime)) { break; } - return new HistoryInfo - ( - 0, - commandLine, - executionStatus, - startExecutionTime, - endExecutionTime - ); + return new HistoryInfo( + pipelineId: 0, + commandLine, + executionStatus, + startExecutionTime, + endExecutionTime + ); } while (false); - // If we are here, an error has occured. + // If we are here, an error has occurred. Exception ex = new InvalidDataException ( @@ -1583,10 +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 { @@ -1598,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 { @@ -1616,15 +1536,13 @@ public int[] Id /// /// Id of a history entry. /// - private 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 { @@ -1642,14 +1560,13 @@ public string[] CommandLine /// /// Commandline parameter. /// - 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 @@ -1677,7 +1594,6 @@ public int Count /// /// Specifies whether new entries to be cleared or the default old ones. /// - [Parameter(Mandatory = false, HelpMessage = "Specifies whether new entries to be cleared or the default old ones.")] public SwitchParameter Newest { @@ -1695,7 +1611,6 @@ public SwitchParameter Newest /// /// Switch parameter on the history entries. /// - private SwitchParameter _newest; #endregion Command Line Parameters @@ -1703,7 +1618,6 @@ public SwitchParameter Newest /// /// Overriding Begin Processing. /// - protected override void BeginProcessing() { _history = ((LocalRunspace)Context.CurrentRunspace).History; @@ -1712,7 +1626,6 @@ protected override void BeginProcessing() /// /// Overriding Process Record. /// - protected override void ProcessRecord() { // case statement to identify the parameter set @@ -1738,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) @@ -1826,9 +1739,9 @@ private void ClearHistoryByID() else { // confirmation message if all the clearhistory cmdlet is used without any parameters - if (_countParameterSpecified == false) + if (!_countParameterSpecified) { - string message = StringUtil.Format(HistoryStrings.ClearHistoryWarning, "Warning");// "The command would clear all the entry(s) from the session history,Are you sure you want to continue ?"; + string message = StringUtil.Format(HistoryStrings.ClearHistoryWarning, "Warning"); // "The command would clear all the entry(s) from the session history,Are you sure you want to continue ?"; if (!ShouldProcess(message)) { return; @@ -1846,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 @@ -1909,13 +1822,12 @@ private void ClearHistoryByCmdLine() /// /// Clears the session history based on the input parameter - /// Id of the entry to be cleared. - /// Count of entries to be cleared. - /// Cmdline string to be cleared. - /// Order of the entries. - /// Nothing. /// - + /// Nothing. + /// Id of the entry to be cleared. + /// Count of entries to be cleared. + /// Cmdline string to be cleared. + /// Order of the entries. private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParameter newest) { // if cmdline is null,use default parameter set notion. @@ -1968,7 +1880,7 @@ private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParam // Clear the History value. foreach (HistoryInfo entry in _entries) { - if (entry != null && entry.Cleared == false) + if (entry != null && !entry.Cleared) _history.ClearEntry(entry.Id); } @@ -1988,4 +1900,3 @@ private void ClearHistoryEntries(long id, int count, string cmdline, SwitchParam #endregion Private } } - diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index ecc4780c284..8e5d30be71f 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -1,38 +1,19 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Internal; -using System.Management.Automation.Language; using System.Management.Automation.Runspaces; +using System.Management.Automation.Subsystem.Feedback; using System.Runtime.InteropServices; -using System.Security; using System.Text; -using System.Text.RegularExpressions; - -using Microsoft.PowerShell.Commands; using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { - internal enum SuggestionMatchType - { - /// Match on a command. - Command = 0, - /// Match based on exception message. - Error = 1, - /// Match by running a script block. - Dynamic = 2, - - /// Match by fully qualified ErrorId. - ErrorId = 3 - } - #region Public HostUtilities Class /// @@ -42,81 +23,25 @@ public static class HostUtilities { #region Internal Access - private static string s_checkForCommandInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param() - - $foundSuggestion = $false - - if($lastError -and - ($lastError.Exception -is ""System.Management.Automation.CommandNotFoundException"")) - { - $escapedCommand = [System.Management.Automation.WildcardPattern]::Escape($lastError.TargetObject) - $foundSuggestion = @(Get-Command ($ExecutionContext.SessionState.Path.Combine(""."", $escapedCommand)) -ErrorAction Ignore).Count -gt 0 - } - - $foundSuggestion - "; - - private static string s_createCommandExistsInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) - - $formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)"" - "; - - private static string s_getFuzzyMatchedCommands = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) + private static readonly char s_actionIndicator = HostSupportUnicode() ? '\u27a4' : '>'; - $formatString -f [string]::Join(', ', (Get-Command $lastError.TargetObject -UseFuzzyMatch | Select-Object -First 10 -Unique -ExpandProperty Name)) - "; - - private static List s_suggestions = InitializeSuggestions(); - - private static List InitializeSuggestions() + private static bool HostSupportUnicode() { - 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")) + // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - suggestions.Add( - NewSuggestion( - id: 4, - category: "General", - matchType: SuggestionMatchType.ErrorId, - rule: "CommandNotFoundException", - suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_getFuzzyMatchedCommands, isProductCode: true), - suggestionArgs: new object[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandNotFound) }, - enabled: true)); + return 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"; } - return suggestions; + string ctype = Environment.GetEnvironmentVariable("LC_ALL") ?? + Environment.GetEnvironmentVariable("LC_CTYPE") ?? + Environment.GetEnvironmentVariable("LANG") ?? + string.Empty; + + return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); } #region GetProfileCommands @@ -138,16 +63,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. /// @@ -233,10 +148,11 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse else { basePath = GetAllUsersFolderPath(shellId); - if (string.IsNullOrEmpty(basePath)) - { - return string.Empty; - } + } + + if (string.IsNullOrEmpty(basePath)) + { + return string.Empty; } string profileName = useTestProfile ? "profile_test.ps1" : "profile.ps1"; @@ -307,376 +223,6 @@ internal static string GetMaxLines(string source, int maxLines) return returnValue.ToString(); } - internal static List GetSuggestion(Runspace runspace) - { - LocalRunspace localRunspace = runspace as LocalRunspace; - if (localRunspace == null) { return new List(); } - - // Get the last value of $? - bool questionMarkVariableValue = localRunspace.ExecutionContext.QuestionMarkVariableValue; - - // Get the last history item - History history = localRunspace.History; - HistoryInfo[] entries = history.GetEntries(-1, 1, true); - - if (entries.Length == 0) - return new List(); - - HistoryInfo lastHistory = entries[0]; - - // Get the last error - ArrayList errorList = (ArrayList)localRunspace.GetExecutionContext.DollarErrorVariable; - object lastError = null; - - if (errorList.Count > 0) - { - lastError = errorList[0] as Exception; - ErrorRecord lastErrorRecord = null; - - // The error was an actual ErrorRecord - if (lastError == null) - { - lastErrorRecord = errorList[0] as ErrorRecord; - } - else if (lastError is RuntimeException) - { - lastErrorRecord = ((RuntimeException)lastError).ErrorRecord; - } - - // If we got information about the error invocation, - // we can be more careful with the errors we pass along - if ((lastErrorRecord != null) && (lastErrorRecord.InvocationInfo != null)) - { - if (lastErrorRecord.InvocationInfo.HistoryId == lastHistory.Id) - lastError = lastErrorRecord; - else - lastError = null; - } - } - - Runspace oldDefault = null; - bool changedDefault = false; - if (Runspace.DefaultRunspace != runspace) - { - oldDefault = Runspace.DefaultRunspace; - changedDefault = true; - Runspace.DefaultRunspace = runspace; - } - - List suggestions = null; - - try - { - suggestions = GetSuggestion(lastHistory, lastError, errorList); - } - finally - { - if (changedDefault) - { - Runspace.DefaultRunspace = oldDefault; - } - } - - // Restore $? - localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkVariableValue; - return suggestions; - } - - [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] - internal static List GetSuggestion(HistoryInfo lastHistory, object lastError, ArrayList errorList) - { - var returnSuggestions = new List(); - - PSModuleInfo invocationModule = new PSModuleInfo(true); - invocationModule.SessionState.PSVariable.Set("lastHistory", lastHistory); - invocationModule.SessionState.PSVariable.Set("lastError", lastError); - - int initialErrorCount = 0; - - // Go through all of the suggestions - foreach (Hashtable suggestion in s_suggestions) - { - initialErrorCount = errorList.Count; - - // Make sure the rule is enabled - if (!LanguagePrimitives.IsTrue(suggestion["Enabled"])) - continue; - - SuggestionMatchType matchType = (SuggestionMatchType)LanguagePrimitives.ConvertTo( - suggestion["MatchType"], - typeof(SuggestionMatchType), - CultureInfo.InvariantCulture); - - // If this is a dynamic match, evaluate the ScriptBlock - if (matchType == SuggestionMatchType.Dynamic) - { - object result = null; - - ScriptBlock evaluator = suggestion["Rule"] as ScriptBlock; - if (evaluator == null) - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.RuleMustBeScriptBlock, "Rule"); - } - - try - { - result = invocationModule.Invoke(evaluator, null); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - suggestion["Enabled"] = false; - continue; - } - - // If it returned results, evaluate its suggestion - if (LanguagePrimitives.IsTrue(result)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - else - { - string matchText = string.Empty; - - // Otherwise, this is a Regex match against the - // command or error - if (matchType == SuggestionMatchType.Command) - { - matchText = lastHistory.CommandLine; - } - else if (matchType == SuggestionMatchType.Error) - { - if (lastError != null) - { - Exception lastException = lastError as Exception; - if (lastException != null) - { - matchText = lastException.Message; - } - else - { - matchText = lastError.ToString(); - } - } - } - else if (matchType == SuggestionMatchType.ErrorId) - { - if (lastError != null && lastError is ErrorRecord errorRecord) - { - matchText = errorRecord.FullyQualifiedErrorId; - } - } - else - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.InvalidMatchType, - "MatchType"); - } - - // If the text matches, evaluate the suggestion - if (Regex.IsMatch(matchText, (string)suggestion["Rule"], RegexOptions.IgnoreCase)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - - // If the rule generated an error, disable it - if (errorList.Count != initialErrorCount) - { - suggestion["Enabled"] = false; - } - } - - return returnSuggestions; - } - - /// - /// 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. - /// - /// Identifier for the suggestion. - /// Category for the suggestion. - /// Suggestion match type. - /// Rule to match. - /// Scriptblock to run that returns the suggestion. - /// Arguments to pass to suggestion scriptblock. - /// True if the suggestion is enabled. - /// Hashtable representing the suggestion. - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["SuggestionArgs"] = suggestionArgs; - result["Enabled"] = enabled; - - return result; - } - - /// - /// Create suggestion with scriptblock rule and suggestion. - /// - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["Enabled"] = enabled; - - return result; - } - - /// - /// Create suggestion with scriptblock rule and scriptblock suggestion with arguments. - /// - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = NewSuggestion(id, category, matchType, rule, suggestion, enabled); - result.Add("SuggestionArgs", suggestionArgs); - - return result; - } - - /// - /// 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. - /// - private static string GetSuggestionText(object suggestion, object[] suggestionArgs, PSModuleInfo invocationModule) - { - if (suggestion is ScriptBlock) - { - ScriptBlock suggestionScript = (ScriptBlock)suggestion; - - object result = null; - try - { - result = invocationModule.Invoke(suggestionScript, suggestionArgs); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - return string.Empty; - } - - return (string)LanguagePrimitives.ConvertTo(result, typeof(string), CultureInfo.CurrentCulture); - } - else - { - return (string)LanguagePrimitives.ConvertTo(suggestion, typeof(string), CultureInfo.CurrentCulture); - } - } - /// /// Returns the prompt used in remote sessions: "[machine]: basePrompt" /// @@ -697,47 +243,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}", 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 string.Format( + CultureInfo.InvariantCulture, + "[{0}@{1}]: {2}", + sshConnectionInfo.UserName, + sshConnectionInfo.ComputerName, + basePrompt); } - return false; -#endif + return string.Format( + CultureInfo.InvariantCulture, + "[{0}]: {1}", + runspace.ConnectionInfo.ComputerName, + basePrompt); } /// @@ -897,6 +415,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 9325f95ccad..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 { /// @@ -59,7 +59,10 @@ public string Message return _message; } - set { _message = value; } + set + { + _message = value; + } } /// @@ -156,7 +159,7 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) } } - [DataMember()] + [DataMember] private string _message; private InvocationInfo _invocationInfo; @@ -167,7 +170,7 @@ internal virtual void ToPSObjectForRemoting(PSObject psObject) /// /// A warning record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class WarningRecord : InformationalRecord { /// @@ -217,13 +220,13 @@ public string FullyQualifiedWarningId } } - private string _fullyQualifiedWarningId; + private readonly string _fullyQualifiedWarningId; } /// /// A debug record in the PSInformationalBuffers. /// - [DataContract()] + [DataContract] public class DebugRecord : InformationalRecord { /// @@ -244,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 e170a3792e4..c60bff90303 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs @@ -39,7 +39,7 @@ internal class InternalHost : PSHost, IHostSupportsInteractiveSession internal InternalHost(PSHost externalHost, ExecutionContext executionContext) { Dbg.Assert(externalHost != null, "must supply an PSHost"); - Dbg.Assert(!(externalHost is InternalHost), "try to create an InternalHost from another InternalHost"); + Dbg.Assert(externalHost is not InternalHost, "try to create an InternalHost from another InternalHost"); Dbg.Assert(executionContext != null, "must supply an ExecutionContext"); @@ -448,8 +448,7 @@ public override void NotifyEndApplication() /// private IHostSupportsInteractiveSession GetIHostSupportsInteractiveSession() { - IHostSupportsInteractiveSession host = _externalHostRef.Value as IHostSupportsInteractiveSession; - if (host == null) + if (_externalHostRef.Value is not IHostSupportsInteractiveSession host) { throw new PSNotImplementedException(); } @@ -538,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(); @@ -565,14 +567,14 @@ internal PSHost ExternalHost internal int NestedPromptCount { get; private set; } // Masked variables. - private ObjectRef _externalHostRef; - private ObjectRef _internalUIRef; + private readonly ObjectRef _externalHostRef; + private readonly ObjectRef _internalUIRef; // Private variables. private string _nameResult; private Version _versionResult; private Guid _idResult; - private Stack _contextStack = new Stack(); + private readonly Stack _contextStack = new Stack(); private readonly Guid _zeroGuid; } diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs index d182ef6302a..69346ae303b 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostRawUserInterface.cs @@ -78,7 +78,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override ConsoleColor BackgroundColor @@ -114,7 +113,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Coordinates CursorPosition @@ -150,7 +148,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Coordinates WindowPosition @@ -186,7 +183,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override int CursorSize @@ -222,7 +218,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Size BufferSize @@ -258,7 +253,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Size WindowSize @@ -294,7 +288,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Size MaxWindowSize @@ -320,7 +313,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override Size MaxPhysicalWindowSize @@ -348,7 +340,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override KeyInfo ReadKey(ReadKeyOptions options) @@ -386,7 +377,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override void FlushInputBuffer() @@ -407,7 +397,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override bool KeyAvailable @@ -433,7 +422,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override string WindowTitle @@ -495,7 +483,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override void SetBufferContents(Rectangle r, BufferCell fill) @@ -517,7 +504,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override BufferCell[,] GetBufferContents(Rectangle r) @@ -545,7 +531,6 @@ public override /// if the RawUI property of the external host is null, possibly because the PSHostRawUserInterface is not /// implemented by the external host /// - public override void ScrollBufferContents @@ -624,7 +609,7 @@ public override return _externalRawUI.LengthInBufferCells(character); } - private PSHostRawUserInterface _externalRawUI; - private InternalHost _parentHost; + private readonly PSHostRawUserInterface _externalRawUI; + private readonly InternalHost _parentHost; } } diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs index 1e2478d7bd2..d8f2ef0bf90 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs @@ -49,8 +49,7 @@ class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleCh _internalRawUI.ThrowNotInteractive(); } - private - void + private static void ThrowPromptNotInteractive(string promptMessage) { string message = StringUtil.Format(HostInterfaceExceptionsStrings.HostFunctionPromptNotImplemented, promptMessage); @@ -67,7 +66,6 @@ class InternalHostUserInterface : PSHostUserInterface, IHostUISupportsMultipleCh /// /// /// - public override System.Management.Automation.Host.PSHostRawUserInterface RawUI @@ -80,7 +78,7 @@ public override public override bool SupportsVirtualTerminal { - get { return (_externalUI != null) ? _externalUI.SupportsVirtualTerminal : false; } + get { return _externalUI != null && _externalUI.SupportsVirtualTerminal; } } /// @@ -120,47 +118,6 @@ public override return result; } - /// - /// See base class. - /// - /// - /// The characters typed by the user. - /// - /// - /// If the UI property of the external host is null, possibly because the PSHostUserInterface is not - /// implemented by the external host. - /// - public override - string - ReadLineMaskedAsString() - { - if (_externalUI == null) - { - ThrowNotInteractive(); - } - - string result = null; - - try - { - result = _externalUI.ReadLineMaskedAsString(); - } - catch (PipelineStoppedException) - { - // PipelineStoppedException is thrown by host when it wants - // to stop the pipeline. - LocalPipeline lpl = (LocalPipeline)((RunspaceBase)_parent.Context.CurrentRunspace).GetCurrentlyRunningPipeline(); - if (lpl == null) - { - throw; - } - - lpl.Stopper.Stop(); - } - - return result; - } - /// /// See base class. /// @@ -168,7 +125,6 @@ public override /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not /// implemented by the external host. /// - public override SecureString ReadLineAsSecureString() @@ -209,7 +165,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void Write(string value) @@ -240,7 +195,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) @@ -255,7 +209,14 @@ public override return; } - _externalUI.Write(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.Write(value); + } + else + { + _externalUI.Write(foregroundColor, backgroundColor, value); + } } /// @@ -267,7 +228,6 @@ public override /// if the UI property of the external host is null, possibly because the PSHostUserInterface is not /// implemented by the external host /// - public override void WriteLine() @@ -289,7 +249,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteLine(string value) @@ -337,7 +296,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value) @@ -352,7 +310,14 @@ public override return; } - _externalUI.WriteLine(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.WriteLine(value); + } + else + { + _externalUI.WriteLine(foregroundColor, backgroundColor, value); + } } /// @@ -362,7 +327,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteDebugLine(string message) @@ -388,13 +352,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. @@ -410,7 +368,6 @@ internal void WriteDebugInfoBuffers(DebugRecord record) /// /// If the debug preference is not a valid ActionPreference value. /// - internal void WriteDebugLine(string message, ref ActionPreference preference) @@ -510,7 +467,6 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() /// Preference setting which determines the behaviour. This is by-ref and will be modified based upon what the user /// types. (e.g. YesToAll will change Inquire => NotifyContinue) /// - private bool DebugShouldContinue(string message, ref ActionPreference actionPreference) @@ -553,7 +509,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. @@ -568,7 +524,7 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() endLoop = false; break; } - } while (endLoop != true); + } while (!endLoop); return shouldContinue; } @@ -580,7 +536,6 @@ internal PSInformationalBuffers GetInformationalMessageBuffers() /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteProgress(Int64 sourceId, ProgressRecord record) @@ -591,10 +546,7 @@ public override } // Write to Information Buffers - if (_informationalBuffers != null) - { - _informationalBuffers.AddProgress(record); - } + _informationalBuffers?.AddProgress(record); if (_externalUI == null) { @@ -611,7 +563,6 @@ public override /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteVerboseLine(string message) @@ -642,13 +593,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. @@ -657,7 +602,6 @@ internal void WriteVerboseInfoBuffers(VerboseRecord record) /// if is not null and the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override void WriteWarningLine(string message) { if (message == null) @@ -686,13 +630,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); /// /// @@ -712,13 +650,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) { @@ -734,12 +666,12 @@ internal static Type GetFieldType(FieldDescription field) internal static bool IsSecuritySensitiveType(string typeName) { - if (typeName.Equals(typeof(PSCredential).Name, StringComparison.OrdinalIgnoreCase)) + if (typeName.Equals(nameof(PSCredential), StringComparison.OrdinalIgnoreCase)) { return true; } - if (typeName.Equals(typeof(SecureString).Name, StringComparison.OrdinalIgnoreCase)) + if (typeName.Equals(nameof(SecureString), StringComparison.OrdinalIgnoreCase)) { return true; } @@ -766,7 +698,6 @@ internal static bool IsSecuritySensitiveType(string typeName) /// if the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override Dictionary Prompt(string caption, string message, Collection descriptions) @@ -820,7 +751,6 @@ public override /// if the UI property of the external host is null, /// possibly because the PSHostUserInterface is not implemented by the external host /// - public override int PromptForChoice(string caption, string message, Collection choices, int defaultChoice) @@ -968,7 +898,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, // Construct the caption + message + list of choices + default choices Text.StringBuilder choicesMessage = new Text.StringBuilder(); - char newLine = '\n'; + const char newLine = '\n'; if (!string.IsNullOrEmpty(caption)) { choicesMessage.Append(caption); @@ -984,7 +914,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, string[,] hotkeysAndPlainLabels = null; HostUIHelperMethods.BuildHotkeysAndPlainLabels(choices, out hotkeysAndPlainLabels); - string choiceTemplate = "[{0}] {1} "; + const string choiceTemplate = "[{0}] {1} "; for (int i = 0; i < hotkeysAndPlainLabels.GetLength(1); ++i) { string choice = @@ -1011,8 +941,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 = ","; } @@ -1020,8 +949,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, if (defaultChoiceKeys.Count == 1) { - defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, - defaultChoicesStr); + defaultPrompt = StringUtil.Format(InternalHostUserInterfaceStrings.DefaultChoice, defaultChoicesStr); } else { @@ -1034,7 +962,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, // read choices from the user Collection result = new Collection(); int choicesSelected = 0; - do + while (true) { string choiceMsg = StringUtil.Format(InternalHostUserInterfaceStrings.ChoiceMessage, choicesSelected); messageToBeDisplayed += choiceMsg; @@ -1050,7 +978,7 @@ private Collection EmulatePromptForMultipleChoice(string caption, // choices to be picked. // user did not pick up any choices..choose the default - if ((result.Count == 0) && (defaultChoiceKeys.Keys.Count >= 0)) + if (result.Count == 0) { // if there's a default, pick that one. foreach (int defaultChoice in defaultChoiceKeys.Keys) @@ -1071,15 +999,14 @@ private Collection EmulatePromptForMultipleChoice(string caption, } // reset messageToBeDisplayed messageToBeDisplayed = string.Empty; - } while (true); + } return result; } - private PSHostUserInterface _externalUI = null; - private InternalHostRawUserInterface _internalRawUI = null; - private InternalHost _parent = null; + private readonly PSHostUserInterface _externalUI = null; + private readonly InternalHostRawUserInterface _internalRawUI = null; + private readonly InternalHost _parent = null; private PSInformationalBuffers _informationalBuffers = null; } } - diff --git a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs index 191a5309cc7..aab1cb2e777 100644 --- a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs +++ b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs @@ -151,7 +151,7 @@ public Collection Add get { return _itemsToAdd; } } - private Collection _itemsToAdd; + private readonly Collection _itemsToAdd; /// /// The list of items to remove when AppyTo is called. @@ -161,7 +161,7 @@ public Collection Remove get { return _itemsToRemove; } } - private Collection _itemsToRemove; + private readonly Collection _itemsToRemove; /// /// The list of items to replace an existing list with. @@ -171,7 +171,7 @@ public Collection Replace get { return _replacementItems; } } - private Collection _replacementItems; + private readonly Collection _replacementItems; /// /// Update the given collection with the items in Add and Remove. @@ -212,15 +212,11 @@ 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); - IList list = collectionToUpdate as IList; - if (list == null) + if (collectionToUpdate is not IList list) { throw PSTraceSource.NewInvalidOperationException(PSListModifierStrings.UpdateFailed); } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 92740acf8dd..822dc552204 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,8 +360,8 @@ public override Debugger Debugger } } - private static string s_debugPreferenceCachePath = Path.Combine(Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell"), "DebugPreference.clixml"); - private static object s_debugPreferenceLockObject = new object(); + private static readonly string s_debugPreferenceCachePath = Path.Combine(Platform.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WindowsPowerShell", "DebugPreference.clixml"); + private static readonly object s_debugPreferenceLockObject = new object(); /// /// DebugPreference serves as a property bag to keep @@ -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, new object[] { }, 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. @@ -941,19 +931,19 @@ private void DoCloseHelper() /// function. If a remote runspace supports disconnect then it will be disconnected /// rather than closed. /// - private void CloseOrDisconnectAllRemoteRunspaces(Func> getRunspaces) + 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)) { ThrottleManager throttleManager = new ThrottleManager(); - throttleManager.ThrottleComplete += delegate (object sender, EventArgs e) - { - remoteRunspaceCloseCompleted.Set(); - }; + throttleManager.ThrottleComplete += (object sender, EventArgs e) => remoteRunspaceCloseCompleted.Set(); foreach (RemoteRunspace remoteRunspace in runspaces) { @@ -972,22 +962,22 @@ private void CloseOrDisconnectAllRemoteRunspaces(Func> getR /// private void StopOrDisconnectAllJobs() { - if (JobRepository.Jobs.Count == 0) { return; } + if (JobRepository.Jobs.Count == 0) + { + return; + } List disconnectRunspaces = new List(); using (ManualResetEvent jobsStopCompleted = new ManualResetEvent(false)) { ThrottleManager throttleManager = new ThrottleManager(); - throttleManager.ThrottleComplete += delegate (object sender, EventArgs e) - { - jobsStopCompleted.Set(); - }; + throttleManager.ThrottleComplete += (object sender, EventArgs e) => jobsStopCompleted.Set(); foreach (Job job in this.JobRepository.Jobs) { // Only stop or disconnect PowerShell jobs. - if (job is PSRemotingJob == false) + if (job is not PSRemotingJob) { continue; } @@ -1016,10 +1006,7 @@ private void StopOrDisconnectAllJobs() } // Disconnect all disconnectable job runspaces found. - CloseOrDisconnectAllRemoteRunspaces(() => - { - return disconnectRunspaces; - }); + CloseOrDisconnectAllRemoteRunspaces(() => disconnectRunspaces); } internal void ReleaseDebugger() @@ -1251,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) { @@ -1283,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 @@ -1312,14 +1294,13 @@ internal AutomationEngine Engine private History _history; [TraceSource("RunspaceInit", "Initialization code for Runspace")] - private static - PSTraceSource s_runspaceInitTracer = + private static readonly PSTraceSource s_runspaceInitTracer = PSTraceSource.GetTracer("RunspaceInit", "Initialization code for Runspace", false); /// /// This ensures all processes have a server/listener. /// - private static RemoteSessionNamedPipeServer s_IPCNamedPipeServer = RemoteSessionNamedPipeServer.IPCNamedPipeServer; + private static readonly RemoteSessionNamedPipeServer s_IPCNamedPipeServer = RemoteSessionNamedPipeServer.IPCNamedPipeServer; #endregion private fields } @@ -1331,7 +1312,7 @@ private static /// internal sealed class StopJobOperationHelper : IThrottleOperation { - private Job _job; + private readonly Job _job; /// /// Internal constructor. @@ -1408,7 +1389,7 @@ private void RaiseOperationCompleteEvent() /// internal sealed class CloseOrDisconnectRunspaceOperationHelper : IThrottleOperation { - private RemoteRunspace _remoteRunspace; + private readonly RemoteRunspace _remoteRunspace; /// /// Internal constructor. @@ -1510,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 @@ -1537,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) { @@ -1569,7 +1549,7 @@ public PSDataCollection ErrorRecords get { return _errors; } } - private PSDataCollection _errors; + private readonly PSDataCollection _errors; #region Serialization /// @@ -1577,30 +1557,13 @@ 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 } #endregion Helper Class } - diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index 84c0c6c6ee9..a9a3a66b859 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); } @@ -249,7 +246,7 @@ protected override void StartPipelineExecution() } default: - Debug.Fail(""); + Debug.Fail(string.Empty); break; } } @@ -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) { @@ -760,7 +753,7 @@ private void StopThreadProc() StopHelper(); } - private PipelineStopper _stopper; + private readonly PipelineStopper _stopper; /// /// Gets PipelineStopper object which maintains stack of PipelineProcessor @@ -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() @@ -1074,9 +1067,9 @@ internal override void SetHistoryString(string historyString) /// ExecutionContext, if it available in TLS /// Null, if ExecutionContext is not available in TLS /// - internal static System.Management.Automation.ExecutionContext GetExecutionContextFromTLS() + internal static ExecutionContext GetExecutionContextFromTLS() { - System.Management.Automation.Runspaces.Runspace runspace = Runspace.DefaultRunspace; + Runspace runspace = Runspace.DefaultRunspace; if (runspace == null) { return null; @@ -1114,31 +1107,6 @@ private LocalRunspace LocalRunspace #endregion private_fields - #region invoke_loop_detection - - /// - /// This is list of HistoryInfo ids which have been executed in - /// this pipeline. - /// - private List _invokeHistoryIds = new List(); - - internal bool PresentInInvokeHistoryEntryList(HistoryInfo entry) - { - return _invokeHistoryIds.Contains(entry.Id); - } - - internal void AddToInvokeHistoryEntryList(HistoryInfo entry) - { - _invokeHistoryIds.Add(entry.Id); - } - - internal void RemoveFromInvokeHistoryEntryList(HistoryInfo entry) - { - _invokeHistoryIds.Remove(entry.Id); - } - - #endregion invoke_loop_detection - #region IDisposable Members /// @@ -1156,7 +1124,7 @@ protected override { try { - if (_disposed == false) + if (!_disposed) { _disposed = true; if (disposing) @@ -1190,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); } @@ -1276,16 +1244,16 @@ public void Dispose() } /// - /// Ensure we release the worker thread. + /// Finalizes an instance of the class. /// ~PipelineThread() { Dispose(); } - private Thread _worker; + private readonly Thread _worker; private ThreadStart _workItem; - private AutoResetEvent _workItemReady; + private readonly AutoResetEvent _workItemReady; private bool _closed; } @@ -1300,13 +1268,13 @@ internal class PipelineStopper /// /// Stack of current executing pipeline processor. /// - private Stack _stack = new Stack(); + private readonly Stack _stack = new Stack(); /// /// Object used for synchronization. /// - private object _syncRoot = new object(); - private LocalPipeline _localPipeline; + private readonly object _syncRoot = new object(); + private readonly LocalPipeline _localPipeline; /// /// Default constructor. @@ -1430,4 +1398,3 @@ internal void Stop() } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/MshHost.cs b/src/System.Management.Automation/engine/hostifaces/MshHost.cs index 0c5ef87436f..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 @@ -34,7 +34,6 @@ namespace System.Management.Automation.Host /// /// /// - public abstract class PSHost { /// @@ -47,7 +46,6 @@ public abstract class PSHost /// /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1. /// - protected PSHost() { // do nothing @@ -66,11 +64,10 @@ 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 { get; @@ -82,12 +79,11 @@ 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. /// - public abstract System.Version Version { get; @@ -97,7 +93,6 @@ public abstract System.Version Version /// Gets a GUID that uniquely identifies this instance of the host. The value should remain invariant for the lifetime of /// this instance. /// - public abstract System.Guid InstanceId { get; @@ -118,7 +113,6 @@ public abstract System.Guid InstanceId /// implementation of PSHostUserInterface for this application. As an alternative, /// for simple scenarios, just returning null is sufficient. /// - public abstract System.Management.Automation.Host.PSHostUserInterface UI { get; @@ -134,7 +128,6 @@ public abstract System.Management.Automation.Host.PSHostUserInterface UI /// The runspace will set the thread current culture to this value each time it starts a pipeline. Thus, cmdlets are /// encouraged to use Thread.CurrentThread.CurrentCulture. /// - public abstract System.Globalization.CultureInfo CurrentCulture { get; @@ -148,7 +141,6 @@ public abstract System.Globalization.CultureInfo CurrentCulture /// /// A CultureInfo object representing the host's current UI culture. Returning null is not allowed. /// - public abstract System.Globalization.CultureInfo CurrentUICulture { get; @@ -167,7 +159,6 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// The exit code accompanying the exit keyword. Typically, after exiting a runspace, a host will also terminate. The /// exitCode parameter can be used to set the host's process exit code. /// - public abstract void SetShouldExit(int exitCode); /// @@ -188,7 +179,6 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// evaluates other commands. It does not create a truly new engine instance with new session state.--> /// /// - public abstract void EnterNestedPrompt(); /// @@ -204,7 +194,6 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// If the UI Property returns a null, the engine should not call this method. /// /// - public abstract void ExitNestedPrompt(); /// @@ -230,7 +219,6 @@ public abstract System.Globalization.CultureInfo CurrentUICulture /// change will not be visible to the host if the host is in another process. Therefore, the implementation of /// get for this property should always return a unique instance. /// - public virtual PSObject PrivateData { get @@ -270,14 +258,12 @@ public virtual PSObject PrivateData /// NotifyBeginApplication. /// /// - public abstract void NotifyBeginApplication(); /// /// Called by the engine to notify the host that the execution of a legacy command has completed. /// /// - public abstract void NotifyEndApplication(); /// @@ -304,11 +290,17 @@ public virtual bool DebuggerEnabled /// This interface needs to be implemented by PSHost objects that want to support the PushRunspace /// and PopRunspace functionality. /// +#nullable enable 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")] @@ -331,6 +323,6 @@ public interface IHostSupportsInteractiveSession /// Returns the current runspace associated with this host. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] - Runspace Runspace { get; } + Runspace? Runspace { get; } } } diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs index 79f4bcff66a..64b200b7d61 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs @@ -15,7 +15,6 @@ namespace System.Management.Automation.Host /// /// Represents an (x,y) coordinate pair. /// - public struct Coordinates { @@ -63,21 +62,20 @@ public int Y } /// - /// Overrides + /// Overrides /// /// /// "a,b" where a and b are the values of the X and Y properties. /// - public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", X, Y); + return string.Create(CultureInfo.InvariantCulture, $"{X},{Y}"); } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -86,7 +84,6 @@ public override /// True if is Coordinates and its X and Y values are the same as those of this instance, /// false if not. /// - public override bool Equals(object obj) @@ -102,12 +99,11 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. /// - public override int GetHashCode() @@ -173,7 +169,6 @@ public override /// /// true if the respective X and Y values are the same, false otherwise. /// - public static bool operator ==(Coordinates first, Coordinates second) @@ -195,7 +190,6 @@ public static /// /// true if any of the respective either X or Y field is not the same, false otherwise. /// - public static bool operator !=(Coordinates first, Coordinates second) @@ -207,7 +201,6 @@ public static /// /// Represents a width and height pair. /// - public struct Size { @@ -255,21 +248,20 @@ public int Height } /// - /// Overloads + /// Overloads /// /// /// "a,b" where a and b are the values of the Width and Height properties. /// - public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "{0},{1}", Width, Height); + return string.Create(CultureInfo.InvariantCulture, $"{Width},{Height}"); } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -278,7 +270,6 @@ public override /// True if is Size and its Width and Height values are the same as those of this instance, /// false if not. /// - public override bool Equals(object obj) @@ -294,7 +285,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -302,7 +293,6 @@ public override /// consider Width the high-order part of a 64-bit in, and /// Height the lower order half. Then use the int64.GetHashCode.--> /// - public override int GetHashCode() @@ -368,7 +358,6 @@ public override /// /// true if the respective Width and Height fields are the same, false otherwise. /// - public static bool operator ==(Size first, Size second) @@ -390,7 +379,6 @@ public static /// /// true if any of the respective Width and Height fields are not the same, false otherwise. /// - public static bool operator !=(Size first, Size second) @@ -403,7 +391,6 @@ public static /// Governs the behavior of /// and /// - [Flags] public enum @@ -412,32 +399,27 @@ public static /// /// Allow Ctrl-C to be processed as a keystroke, as opposed to causing a break event. /// - AllowCtrlC = 0x0001, /// /// Do not display the character for the key in the window when pressed. /// - NoEcho = 0x0002, /// /// Include key down events. Either one of IncludeKeyDown and IncludeKeyUp or both must be specified. /// - IncludeKeyDown = 0x0004, /// /// Include key up events. Either one of IncludeKeyDown and IncludeKeyUp or both must be specified. /// - IncludeKeyUp = 0x0008 } /// /// Defines the states of Control Key. /// - [Flags] public enum ControlKeyStates @@ -445,62 +427,52 @@ enum ControlKeyStates /// /// The right alt key is pressed. /// - RightAltPressed = 0x0001, /// /// The left alt key is pressed. /// - LeftAltPressed = 0x0002, /// /// The right ctrl key is pressed. /// - RightCtrlPressed = 0x0004, /// /// The left ctrl key is pressed. /// - LeftCtrlPressed = 0x0008, /// /// The shift key is pressed. /// - ShiftPressed = 0x0010, /// /// The numlock light is on. /// - NumLockOn = 0x0020, /// /// The scrolllock light is on. /// - ScrollLockOn = 0x0040, /// /// The capslock light is on. /// - CapsLockOn = 0x0080, /// /// The key is enhanced. /// - EnhancedKey = 0x0100 } /// /// Represents information of a keystroke. /// - public struct KeyInfo { @@ -516,7 +488,6 @@ struct KeyInfo /// /// Gets and set device-independent key. /// - public int VirtualKeyCode { get { return virtualKeyCode; } @@ -527,7 +498,6 @@ public int VirtualKeyCode /// /// Gets and set unicode Character of the key. /// - public char Character { get { return character; } @@ -538,7 +508,6 @@ public char Character /// /// State of the control keys. /// - public ControlKeyStates ControlKeyState { get { return controlKeyState; } @@ -549,7 +518,6 @@ public ControlKeyStates ControlKeyState /// /// Gets and set the status of whether this instance is generated by a key pressed or released. /// - public bool KeyDown { get { return keyDown; } @@ -573,7 +541,6 @@ public bool KeyDown /// /// Whether the key is pressed or released /// - public KeyInfo ( @@ -590,20 +557,19 @@ bool keyDown } /// - /// Overloads + /// Overloads /// /// /// "a,b,c,d" where a, b, c, and d are the values of the VirtualKeyCode, Character, ControlKeyState, and KeyDown properties. /// - 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 + /// Overrides /// /// /// object to be compared for equality. @@ -612,7 +578,6 @@ public override /// True if is KeyInfo and its VirtualKeyCode, Character, ControlKeyState, and KeyDown values are the /// same as those of this instance, false if not. /// - public override bool Equals(object obj) @@ -628,7 +593,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -637,7 +602,6 @@ public override /// VirtualKeyCode the lower-order nibbles of a 32-bit int, /// Then use the UInt32.GetHashCode.--> /// - public override int GetHashCode() @@ -672,7 +636,6 @@ public override /// are the same, false otherwise. /// /// - public static bool operator ==(KeyInfo first, KeyInfo second) @@ -697,7 +660,6 @@ public static /// are the different, false otherwise. /// /// - public static bool operator !=(KeyInfo first, KeyInfo second) @@ -711,7 +673,6 @@ public static /// /// - public struct Rectangle { @@ -727,7 +688,6 @@ struct Rectangle /// /// Gets and sets the left side of the rectangle. /// - public int Left { get { return left; } @@ -738,7 +698,6 @@ public int Left /// /// Gets and sets the top of the rectangle. /// - public int Top { get { return top; } @@ -749,7 +708,6 @@ public int Top /// /// Gets and sets the right side of the rectangle. /// - public int Right { get { return right; } @@ -760,7 +718,6 @@ public int Right /// /// Gets and sets the bottom of the rectangle. /// - public int Bottom { get { return bottom; } @@ -787,7 +744,6 @@ public int Bottom /// is less than ; /// is less than /// - public Rectangle(int left, int top, int right, int bottom) { @@ -824,7 +780,6 @@ public int Bottom /// The Coordinates of the lower right corner of the Rectangle /// /// - public Rectangle(Coordinates upperLeft, Coordinates lowerRight) : this(upperLeft.X, upperLeft.Y, lowerRight.X, lowerRight.Y) @@ -832,21 +787,20 @@ public int Bottom } /// - /// Overloads + /// Overloads /// /// /// "a,b ; c,d" where a, b, c, and d are values of the Left, Top, Right, and Bottom properties. /// - 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}"); } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -855,7 +809,6 @@ public override /// True if is Rectangle and its Left, Top, Right, and Bottom values are the same as those of this instance, /// false if not. /// - public override bool Equals(object obj) @@ -871,7 +824,7 @@ public override } /// - /// Overrides + /// Overrides /// /// /// Hash code for this instance. @@ -879,7 +832,6 @@ public override /// (Left XOR Right) the lower order half. Then use the int64.GetHashCode.--> /// /// - public override int GetHashCode() @@ -948,7 +900,6 @@ public override /// /// true if the respective Top, Left, Bottom, and Right fields are the same, false otherwise. /// - public static bool operator ==(Rectangle first, Rectangle second) @@ -972,7 +923,6 @@ public static /// true if any of the respective Top, Left, Bottom, and Right fields are not the same, false otherwise. /// /// - public static bool operator !=(Rectangle first, Rectangle second) @@ -984,7 +934,6 @@ public static /// /// Represents a character, a foregroundColor color, and background color. /// - public struct BufferCell { @@ -1000,7 +949,6 @@ struct BufferCell /// /// Gets and sets the character value. /// - public char Character { get { return character; } @@ -1014,7 +962,6 @@ public char Character /// /// Gets and sets the foreground color. /// - public ConsoleColor ForegroundColor { get { return foregroundColor; } @@ -1025,7 +972,6 @@ public ConsoleColor ForegroundColor /// /// Gets and sets the background color. /// - public ConsoleColor BackgroundColor { get { return backgroundColor; } @@ -1036,7 +982,6 @@ public ConsoleColor BackgroundColor /// /// Gets and sets the type value. /// - public BufferCellType BufferCellType { get { return bufferCellType; } @@ -1060,7 +1005,6 @@ public BufferCellType BufferCellType /// /// The type of this BufferCell object /// - public BufferCell(char character, ConsoleColor foreground, ConsoleColor background, BufferCellType bufferCellType) { @@ -1071,21 +1015,20 @@ public BufferCellType BufferCellType } /// - /// Overloads + /// Overloads /// /// /// "'a' b c d" where a, b, c, and d are the values of the Character, ForegroundColor, BackgroundColor, and Type properties. /// - 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}"); } /// - /// Overrides + /// Overrides /// /// /// object to be compared for equality. @@ -1094,7 +1037,6 @@ public override /// True if is BufferCell and its Character, ForegroundColor, BackgroundColor, and BufferCellType values /// are the same as those of this instance, false if not. /// - public override bool Equals(object obj) @@ -1110,15 +1052,14 @@ public override } /// - /// Overrides + /// Overrides /// /// /// /// Hash code for this instance. /// - /// - + /// public override int GetHashCode() @@ -1148,7 +1089,6 @@ public override /// /// true if the respective Character, ForegroundColor, BackgroundColor, and BufferCellType values are the same, false otherwise. /// - public static bool operator ==(BufferCell first, BufferCell second) @@ -1174,7 +1114,6 @@ public static /// true if any of the respective Character, ForegroundColor, BackgroundColor, and BufferCellType values are not the same, /// false otherwise. /// - public static bool operator !=(BufferCell first, BufferCell second) @@ -1189,33 +1128,29 @@ public static /// Defines three types of BufferCells to accommodate for hosts that use up to two cells /// to display a character in some languages such as Chinese and Japanese. /// - public enum BufferCellType { /// /// Character occupies one BufferCell. /// - Complete, /// /// Character occupies two BufferCells and this is the leading one. /// - Leading, /// /// Preceded by a Leading BufferCell. /// - Trailing } #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. /// @@ -1227,14 +1162,12 @@ public enum /// /// /// - public abstract class PSHostRawUserInterface { /// /// Protected constructor which does nothing. Provided per .Net design guidelines section 4.3.1. /// - protected PSHostRawUserInterface() { @@ -1250,7 +1183,6 @@ class PSHostRawUserInterface /// other properties that take structs (e.g. -Position, -Size), I anticipate that the more common use-case for color /// is to just change the foreground color.--> /// - public abstract ConsoleColor ForegroundColor @@ -1264,7 +1196,6 @@ public abstract /// the screen buffer can have a separate background color. /// /// - public abstract ConsoleColor BackgroundColor @@ -1288,7 +1219,6 @@ public abstract /// /// /// - public abstract Coordinates CursorPosition @@ -1305,7 +1235,6 @@ public abstract /// /// /// - public abstract Coordinates WindowPosition @@ -1318,7 +1247,6 @@ public abstract /// Gets or sets the cursor size as a percentage 0..100. /// /// - public abstract int CursorSize @@ -1335,7 +1263,6 @@ public abstract /// /// /// - public abstract Size BufferSize @@ -1353,7 +1280,6 @@ public abstract /// /// /// - public abstract Size WindowSize @@ -1378,7 +1304,6 @@ public abstract /// /// /// - public abstract Size MaxWindowSize @@ -1400,7 +1325,6 @@ public abstract /// /// /// - public abstract Size MaxPhysicalWindowSize @@ -1416,15 +1340,14 @@ public abstract /// Key stroke when a key is pressed. /// /// - /// + /// /// $Host.UI.RawUI.ReadKey() - /// + /// /// /// /// /// /// - public KeyInfo ReadKey() @@ -1447,17 +1370,16 @@ public abstract /// Neither ReadKeyOptions.IncludeKeyDown nor ReadKeyOptions.IncludeKeyUp is specified. /// /// - /// + /// /// $option = [System.Management.Automation.Host.ReadKeyOptions]"IncludeKeyDown"; /// $host.UI.RawUI.ReadKey($option) - /// + /// /// /// /// /// /// /// - public abstract KeyInfo ReadKey(ReadKeyOptions options); @@ -1468,7 +1390,6 @@ public abstract /// /// /// - public abstract void FlushInputBuffer(); @@ -1482,7 +1403,6 @@ public abstract /// /// /// - public abstract bool KeyAvailable @@ -1493,7 +1413,6 @@ public abstract /// /// Gets or sets the titlebar text of the current view window. /// - public abstract string WindowTitle @@ -1521,7 +1440,6 @@ public abstract /// /// /// - public abstract void SetBufferContents(Coordinates origin, BufferCell[,] contents); @@ -1540,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 @@ -1556,7 +1474,7 @@ public abstract /// } /// } /// } - /// + /// /// /// /// @@ -1566,7 +1484,6 @@ public abstract /// /// /// - public abstract void SetBufferContents(Rectangle rectangle, BufferCell fill); @@ -1605,7 +1522,6 @@ public abstract /// /// /// - public abstract BufferCell[,] GetBufferContents(Rectangle rectangle); @@ -1636,7 +1552,6 @@ public abstract /// /// /// - public abstract void ScrollBufferContents @@ -1670,7 +1585,6 @@ BufferCell fill /// /// /// - public virtual int LengthInBufferCells @@ -1710,7 +1624,6 @@ int offset /// /// /// - public virtual int LengthInBufferCells @@ -1743,7 +1656,6 @@ string source /// /// /// - public virtual int LengthInBufferCells @@ -1775,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 @@ -1789,7 +1701,7 @@ char source /// and , respectively. /// The resulting array is suitable for use with /// and . - /// + /// /// /// /// @@ -1872,7 +1784,7 @@ char source /// /// Creates a 2D array of BufferCells by examining .Character. - /// + /// /// /// /// The number of columns of the resulting array @@ -1912,7 +1824,6 @@ char source /// /// /// - public BufferCell[,] NewBufferCellArray(int width, int height, BufferCell contents) @@ -1994,7 +1905,6 @@ char source /// /// /// - public BufferCell[,] NewBufferCellArray(Size size, BufferCell contents) diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 7650bf68540..5f51ba15751 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -23,7 +23,6 @@ namespace System.Management.Automation.Host /// /// /// - public abstract class PSHostUserInterface { /// @@ -60,29 +59,6 @@ public abstract System.Management.Automation.Host.PSHostRawUserInterface RawUI /// public abstract string ReadLine(); - /// - /// Same as ReadLine except that the input is not echoed to the user while it is collected - /// or is echoed in some obfuscated way, such as showing a dot for each character. - /// - /// - /// The characters typed by the user. - /// - /// - /// Note that credentials (a user name and password) should be gathered with - /// - /// - /// - /// - /// - /// - /// - /// - public virtual string ReadLineMaskedAsString() - { - // Default implementation of the function to maintain backwards compatibility of the base class. - throw new PSNotImplementedException(); - } - /// /// Same as ReadLine, except that the result is a SecureString, and that the input is not echoed to the user while it is /// collected (or is echoed in some obfuscated way, such as showing a dot for each character). @@ -136,10 +112,10 @@ public virtual string ReadLineMaskedAsString() /// /// The default implementation writes a carriage return to the screen buffer. - /// - /// - /// - /// + /// + /// + /// + /// /// public virtual void WriteLine() { @@ -194,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. @@ -256,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 @@ -422,7 +539,7 @@ internal void IgnoreCommand(string commandText, InvocationInfo invocation) private sealed class TranscribeOnlyCookie : IDisposable { - private PSHostUserInterface _ui; + private readonly PSHostUserInterface _ui; private bool _disposed = false; public TranscribeOnlyCookie(PSHostUserInterface ui) @@ -462,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); @@ -535,7 +652,7 @@ private void LogTranscriptHeader(System.Management.Automation.Remoting.PSSenderI Environment.MachineName, Environment.OSVersion.VersionString, string.Join(" ", Environment.GetCommandLineArgs()), - System.Diagnostics.Process.GetCurrentProcess().Id, + Environment.ProcessId, versionInfoFooter.ToString().TrimEnd()); } @@ -643,6 +760,13 @@ internal void TranscribeResult(Runspace sourceRunspace, string resultText) } resultText = resultText.TrimEnd(); + + var text = new ValueStringDecorated(resultText); + if (text.IsDecorated) + { + resultText = text.ToString(OutputRendering.PlainText); + } + foreach (TranscriptionOption transcript in TranscriptionData.Transcripts.Prepend(TranscriptionData.SystemTranscript)) { if (transcript != null) @@ -768,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) @@ -966,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); } } @@ -977,10 +1098,10 @@ internal static TranscriptionOption GetSystemTranscriptOption(TranscriptionOptio } internal static TranscriptionOption systemTranscript = null; - private static object s_systemTranscriptLock = new object(); + private static readonly object s_systemTranscriptLock = new object(); - private static Lazy s_transcriptionSettingCache = new Lazy( - () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + private static readonly Lazy s_transcriptionSettingCache = new Lazy( + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), isThreadSafe: true); private static TranscriptionOption GetTranscriptOptionFromSettings(Transcription transcriptConfig, TranscriptionOption currentTranscript) @@ -1035,6 +1156,11 @@ internal static string GetTranscriptPath(string baseDirectory, bool includeDate) } } + if (string.IsNullOrEmpty(baseDirectory)) + { + return string.Empty; + } + if (includeDate) { baseDirectory = Path.Combine(baseDirectory, DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture)); @@ -1046,8 +1172,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", @@ -1072,15 +1198,14 @@ internal TranscriptionData() PromptText = "PS>"; } - internal List Transcripts - { - get; - private set; - } + internal List Transcripts { get; } internal TranscriptionOption SystemTranscript { get; set; } + internal string CommandBeingIgnored { get; set; } + internal bool IsHelperCommand { get; set; } + internal string PromptText { get; set; } } @@ -1096,32 +1221,17 @@ internal TranscriptionOption() /// /// The path that this transcript is being logged to. /// - internal string Path - { - get - { - return _path; - } - - set - { - _path = value; - // Get the encoding from the file, or default (UTF8-NoBom) - Encoding = Utils.GetEncoding(value); - } - } - - private string _path; + internal string Path { get; set; } /// /// Any output to log for this transcript. /// - internal List OutputToLog { get; private set; } + internal List OutputToLog { get; } /// /// Any output currently being logged for this transcript. /// - internal List OutputBeingLogged { get; private set; } + internal List OutputBeingLogged { get; } /// /// Whether to include time stamp / command separators in @@ -1129,12 +1239,6 @@ internal string Path /// internal bool IncludeInvocationHeader { get; set; } - /// - /// The encoding of this transcript, so that appending to it - /// can be done correctly. - /// - internal Encoding Encoding { get; private set; } - /// /// Logs buffered content to disk. We use this instead of File.AppendAllLines /// so that we don't need to pay seek penalties all the time, and so that we @@ -1142,6 +1246,13 @@ internal string Path /// internal void FlushContentToDisk() { + static Encoding GetPathEncoding(string path) + { + using StreamReader reader = new StreamReader(path, Encoding.Default, detectEncodingFromByteOrderMarks: true); + _ = reader.Read(); + return reader.CurrentEncoding; + } + lock (OutputBeingLogged) { if (!_disposed) @@ -1150,11 +1261,13 @@ internal void FlushContentToDisk() { try { + var currentEncoding = GetPathEncoding(this.Path); + // Try to first open the file with permissions that will allow us to read from it // later. _contentWriter = new StreamWriter( new FileStream(this.Path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read), - this.Encoding); + currentEncoding); _contentWriter.BaseStream.Seek(0, SeekOrigin.End); } catch (IOException) @@ -1163,7 +1276,7 @@ internal void FlushContentToDisk() // file permissions. _contentWriter = new StreamWriter( new FileStream(this.Path, FileMode.Append, FileAccess.Write, FileShare.Read), - this.Encoding); + Encoding.Default); } _contentWriter.AutoFlush = true; @@ -1186,7 +1299,10 @@ internal void FlushContentToDisk() /// 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) @@ -1201,8 +1317,20 @@ public void Dispose() if (_contentWriter != null) { - _contentWriter.Flush(); - _contentWriter.Dispose(); + try + { + _contentWriter.Flush(); + _contentWriter.Dispose(); + } + catch (ObjectDisposedException) + { + // Do nothing + } + catch (IOException) + { + // Do nothing + } + _contentWriter = null; } @@ -1217,6 +1345,7 @@ public void Dispose() /// by giving the user ability to select more than one choice. The PromptForChoice method available /// in PSHostUserInterface class supports only one choice selection. /// +#nullable enable public interface IHostUISupportsMultipleChoiceSelection { /// @@ -1241,9 +1370,10 @@ public interface IHostUISupportsMultipleChoiceSelection /// implementation. /// /// - Collection PromptForChoice(string caption, string message, - Collection choices, IEnumerable defaultChoices); + Collection PromptForChoice(string? caption, string? message, + Collection choices, IEnumerable? defaultChoices); } +#nullable restore /// /// Helper methods used by PowerShell's Hosts: ConsoleHost and InternalHost to process @@ -1275,7 +1405,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()); } @@ -1291,7 +1421,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; } @@ -1308,7 +1438,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"); @@ -1348,4 +1478,3 @@ internal static int DetermineChoicePicked(string response, Collection - /// 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 781e53eaf51..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,12 +334,26 @@ public PSCommand AddParameter(string parameterName) new object[] { "PSCommand" }); } - if (_owner != null) + _owner?.AssertChangesAreAccepted(); + + _currentCommand.Parameters.Add(parameterName, true); + return this; + } + + /// + /// Adds a instance to the last added command. + /// + internal PSCommand AddParameter(CommandParameter parameter) + { + if (_currentCommand == null) { - _owner.AssertChangesAreAccepted(); + throw PSTraceSource.NewInvalidOperationException(PSCommandStrings.ParameterRequiresCommand, + new object[] { "PSCommand" }); } - _currentCommand.Parameters.Add(parameterName, true); + _owner?.AssertChangesAreAccepted(); + + _currentCommand.Parameters.Add(parameter); return this; } @@ -363,8 +361,9 @@ public PSCommand AddParameter(string parameterName) /// 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 @@ -392,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 51d74dcc269..a0945e21df1 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs @@ -116,12 +116,11 @@ 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 - private IList _data; + private readonly IList _data; private ManualResetEvent _readWaitHandle; private bool _isOpen = true; private bool _releaseOnEnumeration; @@ -336,9 +335,7 @@ protected PSDataCollection(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - IList listToUse = info.GetValue("Data", typeof(IList)) as IList; - - if (listToUse == null) + if (info.GetValue("Data", typeof(IList)) is not IList listToUse) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } @@ -392,7 +389,10 @@ public bool IsOpen /// public int DataAddedCount { - get { return _dataAddedFrequency; } + get + { + return _dataAddedFrequency; + } set { @@ -552,19 +552,12 @@ 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 - EventHandler tempCompleted = Completed; - if (tempCompleted != null) - { - tempCompleted(this, EventArgs.Empty); - } + Completed?.Invoke(this, EventArgs.Empty); } if (raiseDataAdded) @@ -801,10 +794,7 @@ public void Clear() { lock (SyncObject) { - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } @@ -1325,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); } } @@ -1405,22 +1392,14 @@ private void RaiseDataAddingEvent(Guid psInstanceId, object itemAdded) { // A temporary variable is used as the DataAdding may // reach null (because of -='s) after the null check - EventHandler tempDataAdding = DataAdding; - if (tempDataAdding != null) - { - tempDataAdding(this, new DataAddingEventArgs(psInstanceId, itemAdded)); - } + DataAdding?.Invoke(this, new DataAddingEventArgs(psInstanceId, itemAdded)); } private void RaiseDataAddedEvent(Guid psInstanceId, int index) { // A temporary variable is used as the DataAdded may // reach null (because of -='s) after the null check - EventHandler tempDataAdded = DataAdded; - if (tempDataAdded != null) - { - tempDataAdded(this, new DataAddedEventArgs(psInstanceId, index)); - } + DataAdded?.Invoke(this, new DataAddedEventArgs(psInstanceId, index)); } /// @@ -1533,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; } @@ -1568,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. @@ -1634,7 +1613,7 @@ private static void VerifyValueType(object value) throw PSTraceSource.NewArgumentNullException(nameof(value), PSDataBufferStrings.ValueNullReference); } } - else if (!(value is T)) + else if (value is not T) { throw PSTraceSource.NewArgumentException(nameof(value), PSDataBufferStrings.CannotConvertToGenericType, value.GetType().FullName, @@ -1643,7 +1622,7 @@ private static void VerifyValueType(object value) } // Serializes an object, as long as it's not serialized. - private PSObject GetSerializedObject(object value) + private static PSObject GetSerializedObject(object value) { // This is a safe cast, as this method is only called with "SerializeInput" is set, // and that method throws if the collection type is not PSObject. @@ -1668,7 +1647,7 @@ private PSObject GetSerializedObject(object value) } } - private bool SerializationWouldHaveNoEffect(PSObject result) + private static bool SerializationWouldHaveNoEffect(PSObject result) { if (result == null) { @@ -1806,10 +1785,7 @@ protected void Dispose(bool disposing) _readWaitHandle = null; } - if (_data != null) - { - _data.Clear(); - } + _data?.Clear(); } } } @@ -1823,8 +1799,8 @@ protected void Dispose(bool disposing) /// Needed to provide a way to get to the non-blocking /// MoveNext implementation. /// - /// - internal interface IBlockingEnumerator : IEnumerator + /// + internal interface IBlockingEnumerator : IEnumerator { bool MoveNext(bool block); } @@ -1836,15 +1812,15 @@ internal interface IBlockingEnumerator : IEnumerator /// either all the PowerShell operations are completed or the /// PSDataCollection is closed. /// - /// - internal sealed class PSDataCollectionEnumerator : IBlockingEnumerator + /// + internal sealed class PSDataCollectionEnumerator : IBlockingEnumerator { #region Private Data - private W _currentElement; + private T _currentElement; private int _index; - private PSDataCollection _collToEnumerate; - private bool _neverBlock; + private readonly PSDataCollection _collToEnumerate; + private readonly bool _neverBlock; #endregion @@ -1859,7 +1835,7 @@ internal sealed class PSDataCollectionEnumerator : IBlockingEnumerator /// /// Controls if the enumerator is blocking by default or not. /// - internal PSDataCollectionEnumerator(PSDataCollection collection, bool neverBlock) + internal PSDataCollectionEnumerator(PSDataCollection collection, bool neverBlock) { Dbg.Assert(collection != null, "Collection cannot be null"); @@ -1868,7 +1844,7 @@ internal PSDataCollectionEnumerator(PSDataCollection collection, bool neverBl _collToEnumerate = collection; _index = 0; - _currentElement = default(W); + _currentElement = default(T); _collToEnumerate.IsEnumerated = true; _neverBlock = neverBlock; } @@ -1886,7 +1862,7 @@ internal PSDataCollectionEnumerator(PSDataCollection collection, bool neverBl /// if the enumerator is positioned before the first element or after /// the last element; the value of the property is undefined. /// - W IEnumerator.Current + T IEnumerator.Current { get { @@ -1925,7 +1901,7 @@ public object Current /// public bool MoveNext() { - return MoveNext(_neverBlock == false); + return MoveNext(!_neverBlock); } /// @@ -1940,14 +1916,14 @@ public bool MoveNext(bool block) { lock (_collToEnumerate.SyncObject) { - do + while (true) { if (_index < _collToEnumerate.Count) { _currentElement = _collToEnumerate[_index]; if (_collToEnumerate.ReleaseOnEnumeration) { - _collToEnumerate[_index] = default(W); + _collToEnumerate[_index] = default(T); } _index++; @@ -1956,7 +1932,7 @@ public bool MoveNext(bool block) // we have reached the end if either the collection is closed // or no powershell instance is bound to this collection. - if ((0 == _collToEnumerate.RefCount) || (!_collToEnumerate.IsOpen)) + if ((_collToEnumerate.RefCount == 0) || (!_collToEnumerate.IsOpen)) { return false; } @@ -1979,7 +1955,7 @@ public bool MoveNext(bool block) { return false; } - } while (true); + } } } @@ -1989,7 +1965,7 @@ public bool MoveNext(bool block) /// public void Reset() { - _currentElement = default(W); + _currentElement = default(T); _index = 0; } @@ -2010,7 +1986,7 @@ void IDisposable.Dispose() /// internal sealed class PSInformationalBuffers { - private Guid _psInstanceId; + private readonly Guid _psInstanceId; /// /// Default constructor. @@ -2042,7 +2018,10 @@ internal PSInformationalBuffers(Guid psInstanceId) /// internal PSDataCollection Progress { - get { return progress; } + get + { + return progress; + } set { @@ -2058,7 +2037,10 @@ internal PSDataCollection Progress /// internal PSDataCollection Verbose { - get { return verbose; } + get + { + return verbose; + } set { @@ -2074,7 +2056,10 @@ internal PSDataCollection Verbose /// internal PSDataCollection Debug { - get { return debug; } + get + { + return debug; + } set { @@ -2101,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 a25e4e2c840..56bdabbff14 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 } @@ -539,7 +543,7 @@ private PSTaskDataStreamWriter() { } public PSTaskDataStreamWriter(PSCmdlet psCmdlet) { _cmdlet = psCmdlet; - _cmdletThreadId = Thread.CurrentThread.ManagedThreadId; + _cmdletThreadId = Environment.CurrentManagedThreadId; _dataStream = new PSDataCollection(); } @@ -605,7 +609,7 @@ public void Close() private void CheckCmdletThread() { - if (Thread.CurrentThread.ManagedThreadId != _cmdletThreadId) + if (Environment.CurrentManagedThreadId != _cmdletThreadId) { throw new PSInvalidOperationException(InternalCommandStrings.PSTaskStreamWriterWrongThread); } @@ -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/Parameter.cs b/src/System.Management.Automation/engine/hostifaces/Parameter.cs index 3f7d072318a..8511d13c050 100644 --- a/src/System.Management.Automation/engine/hostifaces/Parameter.cs +++ b/src/System.Management.Automation/engine/hostifaces/Parameter.cs @@ -80,9 +80,10 @@ public CommandParameter(string name, object value) #endregion Public properties - #region Private Fields - - #endregion Private Fields + /// + /// Gets whether the parameter was from splatting a Hashtable. + /// + private bool FromHashtableSplatting { get; set; } #region Conversion from and to CommandParameterInternal @@ -100,7 +101,7 @@ internal static CommandParameter FromCommandParameterInternal(CommandParameterIn name = internalParameter.ParameterText; if (internalParameter.SpaceAfterParameter) { - name = name + " "; + name += " "; } Diagnostics.Assert(name != null, "'name' variable should be initialized at this point"); @@ -108,17 +109,14 @@ internal static CommandParameter FromCommandParameterInternal(CommandParameterIn Diagnostics.Assert(name.Trim().Length != 1, "Parameter name has to have some non-whitespace characters in it"); } - if (internalParameter.ParameterAndArgumentSpecified) - { - return new CommandParameter(name, internalParameter.ArgumentValue); - } + CommandParameter result = internalParameter.ParameterAndArgumentSpecified + ? new CommandParameter(name, internalParameter.ArgumentValue) + : name != null + ? new CommandParameter(name) + : new CommandParameter(name: null, internalParameter.ArgumentValue); - if (name != null) // either a switch parameter or first part of parameter+argument - { - return new CommandParameter(name); - } - // either a positional argument or second part of parameter+argument - return new CommandParameter(null, internalParameter.ArgumentValue); + result.FromHashtableSplatting = internalParameter.FromHashtableSplatting; + return result; } internal static CommandParameterInternal ToCommandParameterInternal(CommandParameter publicParameter, bool forNativeCommand) @@ -143,9 +141,12 @@ internal static CommandParameterInternal ToCommandParameterInternal(CommandParam { parameterText = forNativeCommand ? name : "-" + name; return CommandParameterInternal.CreateParameterWithArgument( - /*parameterAst*/null, name, parameterText, - /*argumentAst*/null, value, - true); + parameterAst: null, + parameterName: name, + parameterText: parameterText, + argumentAst: null, + value: value, + spaceAfterParameter: true); } // if first character of name is '-', then we try to fake the original token @@ -184,9 +185,13 @@ internal static CommandParameterInternal ToCommandParameterInternal(CommandParam // name+value pair return CommandParameterInternal.CreateParameterWithArgument( - /*parameterAst*/null, parameterName, parameterText, - /*argumentAst*/null, value, - spaceAfterParameter); + parameterAst: null, + parameterName, + parameterText, + argumentAst: null, + value, + spaceAfterParameter, + publicParameter.FromHashtableSplatting); } #endregion @@ -233,34 +238,6 @@ internal PSObject ToPSObjectForRemoting() } #endregion - - #region Win Blue Extensions - -#if !CORECLR // PSMI Not Supported On CSS - internal CimInstance ToCimInstance() - { - CimInstance c = InternalMISerializer.CreateCimInstance("PS_Parameter"); - CimProperty nameProperty = InternalMISerializer.CreateCimProperty("Name", this.Name, - Microsoft.Management.Infrastructure.CimType.String); - c.CimInstanceProperties.Add(nameProperty); - Microsoft.Management.Infrastructure.CimType cimType = CimConverter.GetCimType(this.Value.GetType()); - CimProperty valueProperty; - if (cimType == Microsoft.Management.Infrastructure.CimType.Unknown) - { - valueProperty = InternalMISerializer.CreateCimProperty("Value", (object)PSMISerializer.Serialize(this.Value), - Microsoft.Management.Infrastructure.CimType.Instance); - } - else - { - valueProperty = InternalMISerializer.CreateCimProperty("Value", this.Value, cimType); - } - - c.CimInstanceProperties.Add(valueProperty); - return c; - } -#endif - - #endregion Win Blue Extensions } /// @@ -309,4 +286,3 @@ public void Add(string name, object value) } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs index 0a584c6fc36..f7bca01fe7b 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 @@ -113,13 +113,13 @@ public PipelineState ExpectedState /// State of pipeline when exception was thrown. /// [NonSerialized] - private PipelineState _currentState = 0; + private readonly PipelineState _currentState = 0; /// /// States of the pipeline expected in method which throws this exception. /// [NonSerialized] - private PipelineState _expectedState = 0; + private readonly PipelineState _expectedState = 0; } #endregion Exceptions @@ -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; @@ -738,4 +738,3 @@ protected virtual #endregion IDisposable Members } } - diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index ba559a5e1f6..3af978ea165 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 @@ -120,7 +120,7 @@ public PSInvocationState CurrentState /// State of powershell when exception was thrown. /// [NonSerialized] - private PSInvocationState _currState = 0; + private readonly PSInvocationState _currState = 0; } #endregion @@ -263,12 +263,12 @@ internal PSInvocationStateInfo Clone() /// /// The current execution state. /// - private PSInvocationState _executionState; + private readonly PSInvocationState _executionState; /// /// Non-null exception if the execution state change was due to an error. /// - private Exception _exceptionReason; + private readonly Exception _exceptionReason; #endregion } @@ -419,7 +419,7 @@ public bool ExposeFlowControlExceptions /// internal class BatchInvocationContext { - private AutoResetEvent _completionEvent; + private readonly AutoResetEvent _completionEvent; /// /// Class constructor. @@ -589,7 +589,7 @@ public sealed class PowerShell : IDisposable private PSDataCollection _errorBuffer; private bool _isDisposed; - private object _syncObject = new object(); + private readonly object _syncObject = new object(); // client remote powershell if the powershell // is executed with a remote runspace pool @@ -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"); /// /// /// @@ -1266,6 +1265,24 @@ public PowerShell AddParameter(string parameterName) } } + /// + /// Adds a instance to the last added command. + /// + internal PowerShell AddParameter(CommandParameter parameter) + { + lock (_syncObject) + { + if (_psCommand.Commands.Count == 0) + { + throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.ParameterRequiresCommand); + } + + AssertChangesAreAccepted(); + _psCommand.AddParameter(parameter); + return this; + } + } + /// /// Adds a set of parameters to the last added command. /// @@ -1352,9 +1369,7 @@ public PowerShell AddParameters(IDictionary parameters) foreach (DictionaryEntry entry in parameters) { - string parameterName = entry.Key as string; - - if (parameterName == null) + if (entry.Key is not string parameterName) { throw PSTraceSource.NewArgumentException(nameof(parameters), PowerShellStrings.KeyMustBeString); } @@ -1368,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 @@ -2220,10 +2236,7 @@ internal void InvokeWithDebugger( { if (addToHistory) { - if (settings == null) - { - settings = new PSInvocationSettings(); - } + settings ??= new PSInvocationSettings(); settings.AddToHistory = true; } @@ -3063,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); @@ -3104,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); @@ -3158,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); @@ -3206,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); @@ -3268,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); @@ -3307,9 +3348,7 @@ public Task> InvokeAsync(PSDataColle /// private IAsyncResult BeginBatchInvoke(PSDataCollection input, PSDataCollection output, PSInvocationSettings settings, AsyncCallback callback, object state) { - PSDataCollection asyncOutput = (object)output as PSDataCollection; - - if (asyncOutput == null) + if ((object)output is not PSDataCollection asyncOutput) { throw PSTraceSource.NewInvalidOperationException(); } @@ -3479,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 { @@ -3504,10 +3541,7 @@ private void BatchInvocationCallback(IAsyncResult result) break; } - if (objs == null) - { - objs = _batchAsyncResult.Output; - } + objs ??= _batchAsyncResult.Output; DoRemainingBatchCommands(objs); } @@ -3641,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 @@ -3656,7 +3698,7 @@ public PSDataCollection EndInvoke(IAsyncResult asyncResult) if ((psAsyncResult == null) || (psAsyncResult.OwnerId != InstanceId) || - (psAsyncResult.IsAssociatedWithAsyncInvoke != true)) + (!psAsyncResult.IsAssociatedWithAsyncInvoke)) { throw PSTraceSource.NewArgumentException(nameof(asyncResult), PowerShellStrings.AsyncResultNotOwned, "IAsyncResult", "BeginInvoke"); @@ -3692,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 @@ -3748,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) @@ -3798,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); @@ -3806,7 +3860,7 @@ public Task StopAsync(AsyncCallback callback, object state) #region Event Handlers /// - /// Handler for state changed changed events for the currently running pipeline. + /// Handler for state changed events for the currently running pipeline. /// /// /// Source of the event. @@ -3826,15 +3880,50 @@ private void PipelineStateChanged(object source, PipelineStateEventArgs stateEve #region IDisposable Overrides /// - /// Dispose all managed resources. This will suppress finalizer on the object from getting called by - /// calling System.GC.SuppressFinalize(this). + /// Release all resources. /// public void Dispose() { - Dispose(true); - // To prevent derived types with finalizers from having to re-implement System.IDisposable to call it, - // unsealed types without finalizers should still call SuppressFinalize. - System.GC.SuppressFinalize(this); + lock (_syncObject) + { + // if already disposed return + if (_isDisposed) + { + return; + } + } + + // Stop the currently running command outside of the lock + if (InvocationStateInfo.State == PSInvocationState.Running || + InvocationStateInfo.State == PSInvocationState.Stopping) + { + Stop(); + } + + lock (_syncObject) + { + _isDisposed = true; + } + + if (OutputBuffer != null && OutputBufferOwner) + { + OutputBuffer.Dispose(); + } + + if (_errorBuffer != null && ErrorBufferOwner) + { + _errorBuffer.Dispose(); + } + + if (IsRunspaceOwner) + { + _runspace.Dispose(); + } + + RemotePowerShell?.Dispose(); + + _invokeAsyncResult = null; + _stopAsyncResult = null; } #endregion @@ -4067,62 +4156,6 @@ private void AssertNotDisposed() } } - /// - /// Release all the resources. - /// - /// - /// if true, release all the managed objects. - /// - private void Dispose(bool disposing) - { - if (disposing) - { - lock (_syncObject) - { - // if already disposed return - if (_isDisposed) - { - return; - } - } - - // Stop the currently running command outside of the lock - if (InvocationStateInfo.State == PSInvocationState.Running || - InvocationStateInfo.State == PSInvocationState.Stopping) - { - Stop(); - } - - lock (_syncObject) - { - _isDisposed = true; - } - - if (OutputBuffer != null && OutputBufferOwner) - { - OutputBuffer.Dispose(); - } - - if (_errorBuffer != null && ErrorBufferOwner) - { - _errorBuffer.Dispose(); - } - - if (IsRunspaceOwner) - { - _runspace.Dispose(); - } - - if (RemotePowerShell != null) - { - RemotePowerShell.Dispose(); - } - - _invokeAsyncResult = null; - _stopAsyncResult = null; - } - } - /// /// Clear the internal elements. /// @@ -4130,10 +4163,7 @@ private void InternalClearSuppressExceptions() { lock (_syncObject) { - if (_worker != null) - { - _worker.InternalClearSuppressExceptions(); - } + _worker?.InternalClearSuppressExceptions(); } } @@ -4259,10 +4289,7 @@ internal void SetStateChanged(PSInvocationStateInfo stateInfo) { if (RunningExtraCommands) { - if (tempInvokeAsyncResult != null) - { - tempInvokeAsyncResult.SetAsCompleted(InvocationStateInfo.Reason); - } + tempInvokeAsyncResult?.SetAsCompleted(InvocationStateInfo.Reason); RaiseStateChangeEvent(InvocationStateInfo.Clone()); } @@ -4270,16 +4297,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) { @@ -4291,7 +4312,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(); @@ -4318,10 +4339,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 @@ -4342,7 +4360,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(); @@ -4387,10 +4405,7 @@ internal void ClearRemotePowerShell() { lock (_syncObject) { - if (RemotePowerShell != null) - { - RemotePowerShell.Clear(); - } + RemotePowerShell?.Clear(); } } @@ -4588,7 +4603,7 @@ private void CoreInvokeRemoteHelper(PSDataCollection in psAsyncResult.EndInvoke(); EndInvokeAsyncResult = null; - if ((PSInvocationState.Failed == InvocationStateInfo.State) && + if ((InvocationStateInfo.State == PSInvocationState.Failed) && (InvocationStateInfo.Reason != null)) { throw InvocationStateInfo.Reason; @@ -4917,7 +4932,7 @@ private IAsyncResult CoreInvokeAsync(PSDataCollection i /// /// Verifies the settings for ThreadOptions and ApartmentState. /// - private void VerifyThreadSettings(PSInvocationSettings settings, ApartmentState runspaceApartmentState, PSThreadOptions runspaceThreadOptions, bool isRemote) + private static void VerifyThreadSettings(PSInvocationSettings settings, ApartmentState runspaceApartmentState, PSThreadOptions runspaceThreadOptions, bool isRemote) { ApartmentState apartmentState; @@ -4971,7 +4986,7 @@ private void Prepare(PSDataCollection input, PSDataColl lock (_syncObject) { - if ((_psCommand == null) || (_psCommand.Commands == null) || (0 == _psCommand.Commands.Count)) + if ((_psCommand == null) || (_psCommand.Commands == null) || (_psCommand.Commands.Count == 0)) { throw PSTraceSource.NewInvalidOperationException(PowerShellStrings.NoCommandToInvoke); } @@ -5104,11 +5119,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); @@ -5169,10 +5181,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(); } /// @@ -5244,7 +5253,7 @@ private bool ServerSupportsBatchInvocation() if (_runspace != null) { return _runspace.RunspaceStateInfo.State != RunspaceState.BeforeOpen && - _runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersionWin8RTM; + _runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersion_2_2; } RemoteRunspacePoolInternal remoteRunspacePoolInternal = null; @@ -5258,7 +5267,7 @@ private bool ServerSupportsBatchInvocation() } return remoteRunspacePoolInternal != null && - remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM; + remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersion_2_2; } /// @@ -5273,10 +5282,7 @@ private void AddToRemoteRunspaceRunningList() else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); - if (remoteRunspacePoolInternal != null) - { - remoteRunspacePoolInternal.PushRunningPowerShell(this); - } + remoteRunspacePoolInternal?.PushRunningPowerShell(this); } } @@ -5292,17 +5298,14 @@ private void RemoveFromRemoteRunspaceRunningList() else { RemoteRunspacePoolInternal remoteRunspacePoolInternal = GetRemoteRunspacePoolInternal(); - if (remoteRunspacePoolInternal != null) - { - remoteRunspacePoolInternal.PopRunningPowerShell(); - } + remoteRunspacePoolInternal?.PopRunningPowerShell(); } } private RemoteRunspacePoolInternal GetRemoteRunspacePoolInternal() { RunspacePool runspacePool = _rsConnection as RunspacePool; - return (runspacePool != null) ? (runspacePool.RemoteRunspacePoolInternal) : null; + return runspacePool?.RemoteRunspacePoolInternal; } #endregion @@ -5315,13 +5318,13 @@ private RemoteRunspacePoolInternal GetRemoteRunspacePoolInternal() /// private sealed class Worker { - private ObjectStreamBase _inputStream; - private ObjectStreamBase _outputStream; - private ObjectStreamBase _errorStream; - private PSInvocationSettings _settings; + private readonly ObjectStreamBase _inputStream; + private readonly ObjectStreamBase _outputStream; + private readonly ObjectStreamBase _errorStream; + private readonly PSInvocationSettings _settings; private bool _isNotActive; - private PowerShell _shell; - private object _syncObject = new object(); + private readonly PowerShell _shell; + private readonly object _syncObject = new object(); /// /// @@ -5532,7 +5535,7 @@ internal bool ConstructPipelineAndDoWork(Runspace rs, bool performSyncInvoke) LocalPipeline localPipeline = new LocalPipeline( lrs, _shell.Commands.Commands, - ((_settings != null) && (_settings.AddToHistory)) ? true : false, + (_settings != null && _settings.AddToHistory), _shell.IsNested, _inputStream, _outputStream, @@ -5664,10 +5667,7 @@ internal void InternalClearSuppressExceptions() else { RunspacePool pool = _shell._rsConnection as RunspacePool; - if (pool != null) - { - pool.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); - } + pool?.ReleaseRunspace(CurrentlyRunningPipeline.Runspace); } CurrentlyRunningPipeline.Dispose(); @@ -5813,7 +5813,7 @@ internal PSObject ToPSObjectForRemoting() return powerShellAsPSObject; } - private List CommandsAsListOfPSObjects(CommandCollection commands, Version psRPVersion) + private static List CommandsAsListOfPSObjects(CommandCollection commands, Version psRPVersion) { List commandsAsListOfPSObjects = new List(commands.Count); foreach (Command command in commands) @@ -5838,10 +5838,7 @@ internal void SuspendIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.SuspendQueue(true); - } + RemotePowerShell.DataStructureHandler?.TransportManager.SuspendQueue(true); } /// @@ -5854,10 +5851,7 @@ internal void ResumeIncomingData() throw new PSNotSupportedException(); } - if (RemotePowerShell.DataStructureHandler != null) - { - RemotePowerShell.DataStructureHandler.TransportManager.ResumeQueue(); - } + RemotePowerShell.DataStructureHandler?.TransportManager.ResumeQueue(); } /// @@ -6126,7 +6120,7 @@ public void ClearStreams() this.Warning.Clear(); } - private PowerShell _powershell; + private readonly PowerShell _powershell; } /// @@ -6145,21 +6139,15 @@ public void ClearStreams() /// internal class PowerShellStopper : IDisposable { - private PipelineBase _pipeline; - private PowerShell _powerShell; + private readonly PipelineBase _pipeline; + private readonly PowerShell _powerShell; private EventHandler _eventHandler; 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 35029df1e06..f86d2c00a54 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,48 +177,49 @@ public bool HasExited #endregion Constructors #region Dispose + /// + /// Release all resources. /// public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - /// - /// - /// - private void Dispose(bool disposing) - { - if (_isDisposed) return; - lock (_syncObject) + if (_isDisposed) { - if (_isDisposed) return; - _isDisposed = true; + return; } - if (disposing) + lock (_syncObject) { - try - { - if (Process != null && !Process.HasExited) - Process.Kill(); - } - catch (InvalidOperationException) - { - } - catch (Win32Exception) - { - } - catch (NotSupportedException) + if (_isDisposed) { + return; } + + _isDisposed = true; + } + + try + { + if (Process != null && !Process.HasExited) + Process.Kill(); + } + catch (InvalidOperationException) + { + } + catch (Win32Exception) + { + } + catch (NotSupportedException) + { } } #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/RunspaceInit.cs b/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs index 04dc65baac2..1d352448f29 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspaceInit.cs @@ -9,7 +9,6 @@ namespace System.Management.Automation.Runspaces /// /// Runspace class for local runspace. /// - internal sealed partial class LocalRunspace : RunspaceBase { @@ -27,4 +26,3 @@ private void InitializeDefaults() } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs index 25c611eba0e..dcce30264ec 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. @@ -161,4 +161,3 @@ protected virtual void Dispose(bool disposing) #endregion IDisposable Members } } - diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 3c88f8b9ff7..e7cfb888a88 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 @@ -182,13 +182,13 @@ private static RunspaceState RunspacePoolStateToRunspaceState(RunspacePoolState /// State of the runspace pool when exception was thrown. /// [NonSerialized] - private RunspacePoolState _currentState = 0; + private readonly RunspacePoolState _currentState = 0; /// /// State of the runspace pool expected in method which throws this exception. /// [NonSerialized] - private RunspacePoolState _expectedState = 0; + private readonly RunspacePoolState _expectedState = 0; } #endregion @@ -504,11 +504,13 @@ public sealed class RunspacePool : IDisposable { #region Private Data - private RunspacePoolInternal _internalPool; - private object _syncObject = new object(); + private readonly RunspacePoolInternal _internalPool; + private readonly object _syncObject = new object(); private event EventHandler InternalStateChanged = null; + private event EventHandler InternalForwardEvent = null; + private event EventHandler InternalRunspaceCreated = null; #endregion @@ -638,7 +640,7 @@ internal RunspacePool( TypeTable typeTable) { // Disconnect-Connect semantics are currently only supported in WSMan transport. - if (!(connectionInfo is WSManConnectionInfo)) + if (connectionInfo is not WSManConnectionInfo) { throw new NotSupportedException(); } @@ -837,12 +839,7 @@ private void OnInternalPoolForwardEvent(object sender, PSEventArgs e) /// private void OnEventForwarded(PSEventArgs e) { - EventHandler eh = InternalForwardEvent; - - if (eh != null) - { - eh(this, e); - } + InternalForwardEvent?.Invoke(this, e); } /// @@ -1006,7 +1003,7 @@ public Collection CreateDisconnectedPowerShells() return _internalPool.CreateDisconnectedPowerShells(this); } - /// + /// /// Returns RunspacePool capabilities. /// /// RunspacePoolCapability. @@ -1202,13 +1199,11 @@ public void EndClose(IAsyncResult asyncResult) } /// - /// Dispose the current runspacepool. + /// Release all resources. /// 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 02f0fffaf30..1b20fc4c85f 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -16,7 +16,7 @@ namespace System.Management.Automation.Runspaces.Internal /// /// Class which supports pooling local powerShell runspaces. /// - internal class RunspacePoolInternal + internal class RunspacePoolInternal : IDisposable { #region Private data @@ -40,7 +40,7 @@ internal class RunspacePoolInternal private static readonly TimeSpan s_defaultCleanupPeriod = new TimeSpan(0, 15, 0); // 15 minutes. private TimeSpan _cleanupInterval; - private Timer _cleanupTimer; + private readonly Timer _cleanupTimer; #endregion @@ -231,10 +231,7 @@ internal virtual PSPrimitiveDictionary GetApplicationPrivateData() { lock (this.syncObject) { - if (_applicationPrivateData == null) - { - _applicationPrivateData = new PSPrimitiveDictionary(); - } + _applicationPrivateData ??= new PSPrimitiveDictionary(); } } @@ -276,7 +273,10 @@ public virtual RunspaceConnectionInfo ConnectionInfo /// public TimeSpan CleanupInterval { - get { return _cleanupInterval; } + get + { + return _cleanupInterval; + } set { @@ -813,13 +813,23 @@ public void ReleaseRunspace(Runspace runspace) } } + /// + /// Release all resources. + /// + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + /// /// Dispose off the current runspace pool. /// /// /// true to release all the internal resources. /// - public virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!_isDisposed) { @@ -1303,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. /// /// @@ -1510,7 +1520,7 @@ protected void ServicePendingRequests(object useCallingThreadState) try { - do + while (true) { lock (ultimateRequestQueue) { @@ -1591,7 +1601,7 @@ protected void ServicePendingRequests(object useCallingThreadState) ultimateRequestQueue.Enqueue(runspaceRequestQueue.Dequeue()); } } - } while (true); + } endOuterWhile:; } finally @@ -1640,12 +1650,7 @@ protected void AssertIfStateIsBeforeOpen() /// protected virtual void OnForwardEvent(PSEventArgs e) { - EventHandler eh = this.ForwardEvent; - - if (eh != null) - { - eh(this, e); - } + this.ForwardEvent?.Invoke(this, e); } /// diff --git a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs index f5155fd7c13..a7acbd0760b 100644 --- a/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs +++ b/src/System.Management.Automation/engine/hostifaces/internalHostuserInterfacesecurity.cs @@ -14,7 +14,6 @@ class InternalHostUserInterface : PSHostUserInterface /// /// See base class. /// - public override PSCredential PromptForCredential @@ -34,7 +33,6 @@ string targetName /// /// See base class. /// - public override PSCredential PromptForCredential @@ -74,4 +72,3 @@ PSCredentialUIOptions options } } } - diff --git a/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs b/src/System.Management.Automation/engine/hostifaces/pipelinebase.cs index 64efdb12e4a..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) @@ -491,7 +491,9 @@ private void CoreInvoke(IEnumerable input, bool syncCall) throw e; } - if (syncCall && !(InputStream is PSDataCollectionStream || InputStream is PSDataCollectionStream)) + if (syncCall + && InputStream is not PSDataCollectionStream + && InputStream is not PSDataCollectionStream) { // Method is called from synchronous invoke. if (input != null) @@ -513,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 @@ -616,7 +618,7 @@ internal void DoConcurrentCheck(bool syncCall, object syncObject, bool isInLock) { PipelineBase currentPipeline = (PipelineBase)RunspaceBase.GetCurrentlyRunningPipeline(); - if (IsNested == false) + if (!IsNested) { if (currentPipeline == null) { @@ -663,7 +665,7 @@ internal void DoConcurrentCheck(bool syncCall, object syncObject, bool isInLock) { if (_performNestedCheck) { - if (syncCall == false) + if (!syncCall) { throw PSTraceSource.NewInvalidOperationException( RunspaceStrings.NestedPipelineInvokeAsync); @@ -688,7 +690,7 @@ internal void DoConcurrentCheck(bool syncCall, object syncObject, bool isInLock) Dbg.Assert(currentPipeline.NestedPipelineExecutionThread != null, "Current pipeline should always have NestedPipelineExecutionThread set"); Thread th = Thread.CurrentThread; - if (currentPipeline.NestedPipelineExecutionThread.Equals(th) == false) + if (!currentPipeline.NestedPipelineExecutionThread.Equals(th)) { throw PSTraceSource.NewInvalidOperationException( RunspaceStrings.NestedPipelineNoParentPipeline); @@ -758,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. @@ -766,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) { @@ -889,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; } @@ -1044,7 +1046,7 @@ protected override { try { - if (_disposed == false) + if (!_disposed) { _disposed = true; if (disposing) diff --git a/src/System.Management.Automation/engine/interpreter/AddInstruction.cs b/src/System.Management.Automation/engine/interpreter/AddInstruction.cs index 9858167aa74..2f7ada9dcfd 100644 --- a/src/System.Management.Automation/engine/interpreter/AddInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/AddInstruction.cs @@ -130,14 +130,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new AddInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new AddInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new AddInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new AddUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new AddUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new AddUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new AddSingle()); - case TypeCode.Double: return s_double ?? (s_double = new AddDouble()); + case TypeCode.Int16: return s_int16 ??= new AddInt16(); + case TypeCode.Int32: return s_int32 ??= new AddInt32(); + case TypeCode.Int64: return s_int64 ??= new AddInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new AddUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new AddUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new AddUInt64(); + case TypeCode.Single: return s_single ??= new AddSingle(); + case TypeCode.Double: return s_double ??= new AddDouble(); default: throw Assert.Unreachable; @@ -263,14 +263,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new AddOvfInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new AddOvfInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new AddOvfInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new AddOvfUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new AddOvfUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new AddOvfUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new AddOvfSingle()); - case TypeCode.Double: return s_double ?? (s_double = new AddOvfDouble()); + case TypeCode.Int16: return s_int16 ??= new AddOvfInt16(); + case TypeCode.Int32: return s_int32 ??= new AddOvfInt32(); + case TypeCode.Int64: return s_int64 ??= new AddOvfInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new AddOvfUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new AddOvfUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new AddOvfUInt64(); + case TypeCode.Single: return s_single ??= new AddOvfSingle(); + case TypeCode.Double: return s_double ??= new AddOvfDouble(); default: throw Assert.Unreachable; diff --git a/src/System.Management.Automation/engine/interpreter/BranchLabel.cs b/src/System.Management.Automation/engine/interpreter/BranchLabel.cs index 919a7b06106..1bae16783a9 100644 --- a/src/System.Management.Automation/engine/interpreter/BranchLabel.cs +++ b/src/System.Management.Automation/engine/interpreter/BranchLabel.cs @@ -19,7 +19,7 @@ namespace System.Management.Automation.Interpreter { - internal struct RuntimeLabel + internal readonly struct RuntimeLabel { public readonly int Index; public readonly int StackDepth; @@ -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 d351a08bfd4..f7173c745ff 100644 --- a/src/System.Management.Automation/engine/interpreter/CallInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/CallInstruction.cs @@ -104,7 +104,7 @@ public static CallInstruction Create(MethodInfo info, ParameterInfo[] parameters } catch (TargetInvocationException tie) { - if (!(tie.InnerException is NotSupportedException)) + if (tie.InnerException is not NotSupportedException) { throw; } @@ -178,7 +178,7 @@ public static void ArrayItemSetter3(Array array, int index0, int index1, int ind private static bool ShouldCache(MethodInfo info) { - return !(info is DynamicMethod); + return info is not DynamicMethod; } /// @@ -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 56aca65df37..5d994d934c5 100644 --- a/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs +++ b/src/System.Management.Automation/engine/interpreter/ControlFlowInstructions.cs @@ -69,7 +69,7 @@ internal sealed class BranchFalseInstruction : OffsetInstruction public override Instruction[] Cache { - get { return s_cache ?? (s_cache = new Instruction[CacheSize]); } + get { return s_cache ??= new Instruction[CacheSize]; } } internal BranchFalseInstruction() @@ -97,7 +97,7 @@ internal sealed class BranchTrueInstruction : OffsetInstruction public override Instruction[] Cache { - get { return s_cache ?? (s_cache = new Instruction[CacheSize]); } + get { return s_cache ??= new Instruction[CacheSize]; } } internal BranchTrueInstruction() @@ -125,7 +125,7 @@ internal sealed class CoalescingBranchInstruction : OffsetInstruction public override Instruction[] Cache { - get { return s_cache ?? (s_cache = new Instruction[CacheSize]); } + get { return s_cache ??= new Instruction[CacheSize]; } } internal CoalescingBranchInstruction() @@ -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]); } @@ -204,7 +201,7 @@ internal abstract class IndexedBranchInstruction : Instruction internal readonly int _labelIndex; - public IndexedBranchInstruction(int labelIndex) + protected IndexedBranchInstruction(int labelIndex) { _labelIndex = labelIndex; } @@ -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(); @@ -554,7 +551,7 @@ public override int Run(InterpretedFrame frame) /// internal sealed class LeaveExceptionHandlerInstruction : IndexedBranchInstruction { - private static LeaveExceptionHandlerInstruction[] s_cache = new LeaveExceptionHandlerInstruction[2 * CacheSize]; + private static readonly LeaveExceptionHandlerInstruction[] s_cache = new LeaveExceptionHandlerInstruction[2 * CacheSize]; private readonly bool _hasValue; diff --git a/src/System.Management.Automation/engine/interpreter/DivInstruction.cs b/src/System.Management.Automation/engine/interpreter/DivInstruction.cs index f6cfb5b265f..11edd140945 100644 --- a/src/System.Management.Automation/engine/interpreter/DivInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/DivInstruction.cs @@ -130,14 +130,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new DivInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new DivInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new DivInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new DivUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new DivUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new DivUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new DivSingle()); - case TypeCode.Double: return s_double ?? (s_double = new DivDouble()); + case TypeCode.Int16: return s_int16 ??= new DivInt16(); + case TypeCode.Int32: return s_int32 ??= new DivInt32(); + case TypeCode.Int64: return s_int64 ??= new DivInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new DivUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new DivUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new DivUInt64(); + case TypeCode.Single: return s_single ??= new DivSingle(); + case TypeCode.Double: return s_double ??= new DivDouble(); default: throw Assert.Unreachable; 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/EqualInstruction.cs b/src/System.Management.Automation/engine/interpreter/EqualInstruction.cs index 639190e4d46..4db34761ce8 100644 --- a/src/System.Management.Automation/engine/interpreter/EqualInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/EqualInstruction.cs @@ -154,25 +154,25 @@ public static Instruction Create(Type type) var typeToUse = type.IsEnum ? Enum.GetUnderlyingType(type) : type; switch (typeToUse.GetTypeCode()) { - case TypeCode.Boolean: return s_boolean ?? (s_boolean = new EqualBoolean()); - case TypeCode.SByte: return s_SByte ?? (s_SByte = new EqualSByte()); - case TypeCode.Byte: return s_byte ?? (s_byte = new EqualByte()); - case TypeCode.Char: return s_char ?? (s_char = new EqualChar()); - case TypeCode.Int16: return s_int16 ?? (s_int16 = new EqualInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new EqualInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new EqualInt64()); + case TypeCode.Boolean: return s_boolean ??= new EqualBoolean(); + case TypeCode.SByte: return s_SByte ??= new EqualSByte(); + case TypeCode.Byte: return s_byte ??= new EqualByte(); + case TypeCode.Char: return s_char ??= new EqualChar(); + case TypeCode.Int16: return s_int16 ??= new EqualInt16(); + case TypeCode.Int32: return s_int32 ??= new EqualInt32(); + case TypeCode.Int64: return s_int64 ??= new EqualInt64(); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new EqualInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new EqualInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new EqualInt64()); + case TypeCode.UInt16: return s_UInt16 ??= new EqualInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new EqualInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new EqualInt64(); - case TypeCode.Single: return s_single ?? (s_single = new EqualSingle()); - case TypeCode.Double: return s_double ?? (s_double = new EqualDouble()); + case TypeCode.Single: return s_single ??= new EqualSingle(); + case TypeCode.Double: return s_double ??= new EqualDouble(); case TypeCode.Object: if (!type.IsValueType) { - return s_reference ?? (s_reference = new EqualReference()); + return s_reference ??= new EqualReference(); } // TODO: Nullable throw new NotImplementedException(); diff --git a/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs b/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs index 9f46323308f..ccd5c3780f7 100644 --- a/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/GreaterThanInstruction.cs @@ -144,17 +144,17 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.SByte: return s_SByte ?? (s_SByte = new GreaterThanSByte()); - case TypeCode.Byte: return s_byte ?? (s_byte = new GreaterThanByte()); - case TypeCode.Char: return s_char ?? (s_char = new GreaterThanChar()); - case TypeCode.Int16: return s_int16 ?? (s_int16 = new GreaterThanInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new GreaterThanInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new GreaterThanInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new GreaterThanUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new GreaterThanUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new GreaterThanUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new GreaterThanSingle()); - case TypeCode.Double: return s_double ?? (s_double = new GreaterThanDouble()); + case TypeCode.SByte: return s_SByte ??= new GreaterThanSByte(); + case TypeCode.Byte: return s_byte ??= new GreaterThanByte(); + case TypeCode.Char: return s_char ??= new GreaterThanChar(); + case TypeCode.Int16: return s_int16 ??= new GreaterThanInt16(); + case TypeCode.Int32: return s_int32 ??= new GreaterThanInt32(); + case TypeCode.Int64: return s_int64 ??= new GreaterThanInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new GreaterThanUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new GreaterThanUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new GreaterThanUInt64(); + case TypeCode.Single: return s_single ??= new GreaterThanSingle(); + case TypeCode.Double: return s_double ??= new GreaterThanDouble(); default: throw Assert.Unreachable; 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/Instruction.cs b/src/System.Management.Automation/engine/interpreter/Instruction.cs index 0a46fd88dfa..f1d8edaeb87 100644 --- a/src/System.Management.Automation/engine/interpreter/Instruction.cs +++ b/src/System.Management.Automation/engine/interpreter/Instruction.cs @@ -17,10 +17,12 @@ namespace System.Management.Automation.Interpreter { +#nullable enable internal interface IInstructionProvider { void AddInstructions(LightCompiler compiler); } +#nullable restore internal abstract class Instruction { diff --git a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs index 286fce26f15..839ccd6f905 100644 --- a/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs +++ b/src/System.Management.Automation/engine/interpreter/InstructionFactory.cs @@ -53,11 +53,17 @@ internal static InstructionFactory GetFactory(Type type) } protected internal abstract Instruction GetArrayItem(); + protected internal abstract Instruction SetArrayItem(); + protected internal abstract Instruction TypeIs(); + protected internal abstract Instruction TypeAs(); + protected internal abstract Instruction DefaultValue(); + protected internal abstract Instruction NewArray(); + protected internal abstract Instruction NewArrayInit(int elementCount); } @@ -81,42 +87,39 @@ private InstructionFactory() { } protected internal override Instruction GetArrayItem() { - return _getArrayItem ?? (_getArrayItem = new GetArrayItemInstruction()); + return _getArrayItem ??= new GetArrayItemInstruction(); } protected internal override Instruction SetArrayItem() { - return _setArrayItem ?? (_setArrayItem = new SetArrayItemInstruction()); + return _setArrayItem ??= new SetArrayItemInstruction(); } protected internal override Instruction TypeIs() { - return _typeIs ?? (_typeIs = new TypeIsInstruction()); + return _typeIs ??= new TypeIsInstruction(); } protected internal override Instruction TypeAs() { - return _typeAs ?? (_typeAs = new TypeAsInstruction()); + return _typeAs ??= new TypeAsInstruction(); } protected internal override Instruction DefaultValue() { - return _defaultValue ?? (_defaultValue = new DefaultValueInstruction()); + return _defaultValue ??= new DefaultValueInstruction(); } protected internal override Instruction NewArray() { - return _newArray ?? (_newArray = new NewArrayInstruction()); + return _newArray ??= new NewArrayInstruction(); } 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 9617c746359..93946bfd2c3 100644 --- a/src/System.Management.Automation/engine/interpreter/InstructionList.cs +++ b/src/System.Management.Automation/engine/interpreter/InstructionList.cs @@ -24,7 +24,7 @@ namespace System.Management.Automation.Interpreter { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] [DebuggerTypeProxy(typeof(InstructionArray.DebugView))] - internal struct InstructionArray + internal readonly struct InstructionArray { internal readonly int MaxStackDepth; internal readonly int MaxContinuationDepth; @@ -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 @@ -156,7 +158,7 @@ internal static InstructionView[] GetInstructionViews(IList instruc } [DebuggerDisplay("{GetValue(),nq}", Name = "{GetName(),nq}", Type = "{GetDisplayType(), nq}")] - internal struct InstructionView + internal readonly struct InstructionView { private readonly int _index; private readonly int _stackDepth; @@ -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(); @@ -312,7 +311,7 @@ public InstructionArray ToArray() _maxStackDepth, _maxContinuationDepth, _instructions.ToArray(), - (_objects != null) ? _objects.ToArray() : null, + _objects?.ToArray(), BuildRuntimeLabels(), _debugCookies ); @@ -341,11 +340,11 @@ public void EmitLoad(bool value) { if ((bool)value) { - Emit(s_true ?? (s_true = new LoadObjectInstruction(value))); + Emit(s_true ??= new LoadObjectInstruction(value)); } else { - Emit(s_false ?? (s_false = new LoadObjectInstruction(value))); + Emit(s_false ??= new LoadObjectInstruction(value)); } } @@ -353,7 +352,7 @@ public void EmitLoad(object value, Type type) { if (value == null) { - Emit(s_null ?? (s_null = new LoadObjectInstruction(null))); + Emit(s_null ??= new LoadObjectInstruction(null)); return; } @@ -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) { @@ -925,7 +879,7 @@ public void EmitLoadField(FieldInfo field) Emit(GetLoadField(field)); } - private Instruction GetLoadField(FieldInfo field) + private static Instruction GetLoadField(FieldInfo field) { lock (s_loadFields) { @@ -1063,7 +1017,7 @@ public void EmitDynamic> s_factories = + private static readonly Dictionary> s_factories = new Dictionary>(); internal static Instruction CreateDynamicInstruction(Type delegateType, CallSiteBinder binder) @@ -1086,9 +1040,9 @@ internal static Instruction CreateDynamicInstruction(Type delegateType, CallSite return new DynamicInstructionN(delegateType, CallSite.Create(delegateType, binder)); } - factory = - (Func) - instructionType.GetMethod("Factory").CreateDelegate(typeof(Func)); + factory = (Func)instructionType + .GetMethod("Factory") + .CreateDelegate(typeof(Func)); s_factories[delegateType] = factory; } @@ -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/InterpretedFrame.cs b/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs index d4d6923da81..977fee85803 100644 --- a/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs +++ b/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs @@ -27,23 +27,19 @@ namespace System.Management.Automation.Interpreter { internal sealed class InterpretedFrame { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly ThreadLocal CurrentFrame = new ThreadLocal(); internal readonly Interpreter Interpreter; internal InterpretedFrame _parent; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] - private int[] _continuations; + private readonly int[] _continuations; private int _continuationIndex; private int _pendingContinuation; private object _pendingValue; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] public readonly object[] Data; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] public readonly StrongBox[] Closure; public int StackIndex; @@ -295,12 +291,12 @@ internal void PopPendingContinuation() internal static MethodInfo GotoMethod { - get { return s_goto ?? (s_goto = typeof(InterpretedFrame).GetMethod("Goto")); } + get { return s_goto ??= typeof(InterpretedFrame).GetMethod("Goto"); } } internal static MethodInfo VoidGotoMethod { - get { return s_voidGoto ?? (s_voidGoto = typeof(InterpretedFrame).GetMethod("VoidGoto")); } + get { return s_voidGoto ??= typeof(InterpretedFrame).GetMethod("VoidGoto"); } } public int VoidGoto(int labelIndex) 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/LessThanInstruction.cs b/src/System.Management.Automation/engine/interpreter/LessThanInstruction.cs index aaf632d0c77..2d6754ff2fd 100644 --- a/src/System.Management.Automation/engine/interpreter/LessThanInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/LessThanInstruction.cs @@ -144,17 +144,17 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.SByte: return s_SByte ?? (s_SByte = new LessThanSByte()); - case TypeCode.Byte: return s_byte ?? (s_byte = new LessThanByte()); - case TypeCode.Char: return s_char ?? (s_char = new LessThanChar()); - case TypeCode.Int16: return s_int16 ?? (s_int16 = new LessThanInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new LessThanInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new LessThanInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new LessThanUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new LessThanUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new LessThanUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new LessThanSingle()); - case TypeCode.Double: return s_double ?? (s_double = new LessThanDouble()); + case TypeCode.SByte: return s_SByte ??= new LessThanSByte(); + case TypeCode.Byte: return s_byte ??= new LessThanByte(); + case TypeCode.Char: return s_char ??= new LessThanChar(); + case TypeCode.Int16: return s_int16 ??= new LessThanInt16(); + case TypeCode.Int32: return s_int32 ??= new LessThanInt32(); + case TypeCode.Int64: return s_int64 ??= new LessThanInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new LessThanUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new LessThanUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new LessThanUInt64(); + case TypeCode.Single: return s_single ??= new LessThanSingle(); + case TypeCode.Double: return s_double ??= new LessThanDouble(); default: throw Assert.Unreachable; diff --git a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs index e5d399bf480..c117fbff33a 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) @@ -221,7 +226,7 @@ public static DebugInfo GetMatchingDebugInfo(DebugInfo[] debugInfos, int index) return null; } // return the last one that is smaller - i = i - 1; + i -= 1; } return debugInfos[i]; @@ -242,8 +247,7 @@ public override string ToString() // TODO: [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] - [Serializable] - internal struct InterpretedFrameInfo + internal readonly struct InterpretedFrameInfo { public readonly string MethodName; @@ -288,7 +292,7 @@ internal sealed class LightCompiler private readonly LightCompiler _parent; - private static LocalDefinition[] s_emptyLocals = Array.Empty(); + private static readonly LocalDefinition[] s_emptyLocals = Array.Empty(); public LightCompiler(int compilationThreshold) { @@ -1025,7 +1029,7 @@ private void CompileConditionalExpression(Expression expr, bool asVoid) #region Loops - private void CompileLoopExpression(Expression expr) + private static void CompileLoopExpression(Expression expr) { // var node = (LoopExpression)expr; // var enterLoop = new EnterLoopInstruction(node, _locals, _compilationThreshold, _instructions.Count); @@ -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) { @@ -1297,8 +1298,7 @@ private bool TryPushLabelBlock(Expression node) private void DefineBlockLabels(Expression node) { - var block = node as BlockExpression; - if (block == null) + if (node is not BlockExpression block) { return; } @@ -1357,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) { @@ -1532,7 +1532,7 @@ private void CompileTryExpression(Expression expr) enterTryInstr.SetTryHandler( new TryCatchFinallyHandler(tryStart, tryEnd, gotoEnd.TargetIndex, startOfFinally.TargetIndex, _instructions.Count, - exHandlers != null ? exHandlers.ToArray() : null)); + exHandlers?.ToArray())); PopLabelBlock(LabelScopeKind.Finally); } else @@ -1574,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; @@ -1601,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; } @@ -2005,7 +2005,8 @@ private void CompileNoLabelPush(Expression expr) case ExpressionType.Index: CompileIndexExpression(expr); break; case ExpressionType.Label: CompileLabelExpression(expr); break; case ExpressionType.RuntimeVariables: CompileRuntimeVariablesExpression(expr); break; - case ExpressionType.Loop: CompileLoopExpression(expr); break; + case ExpressionType.Loop: + CompileLoopExpression(expr); break; case ExpressionType.Switch: CompileSwitchExpression(expr); break; case ExpressionType.Throw: CompileThrowUnaryExpression(expr, expr.Type == typeof(void)); break; case ExpressionType.Try: CompileTryExpression(expr); break; diff --git a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs index 5c96d2f6870..6f9497db987 100644 --- a/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs +++ b/src/System.Management.Automation/engine/interpreter/LightDelegateCreator.cs @@ -169,7 +169,7 @@ internal void Compile(object state) // So change the lambda's delegate type to Func<...> or // Action<...> so it can be called from the LightLambda.Run // methods. - LambdaExpression lambda = (_lambda as LambdaExpression);// ?? (LambdaExpression)((LightLambdaExpression)_lambda).Reduce(); + LambdaExpression lambda = (_lambda as LambdaExpression); // ?? (LambdaExpression)((LightLambdaExpression)_lambda).Reduce(); if (_interpreter != null) { _compiledDelegateType = GetFuncOrAction(lambda); @@ -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/LightLambda.cs b/src/System.Management.Automation/engine/interpreter/LightLambda.cs index 3bf38da35b9..380887a00e4 100644 --- a/src/System.Management.Automation/engine/interpreter/LightLambda.cs +++ b/src/System.Management.Automation/engine/interpreter/LightLambda.cs @@ -32,7 +32,7 @@ namespace System.Management.Automation.Interpreter { internal sealed class LightLambdaCompileEventArgs : EventArgs { - public Delegate Compiled { get; private set; } + public Delegate Compiled { get; } internal LightLambdaCompileEventArgs(Delegate compiled) { diff --git a/src/System.Management.Automation/engine/interpreter/LocalAccess.cs b/src/System.Management.Automation/engine/interpreter/LocalAccess.cs index 7b0711b1930..30596866c0c 100644 --- a/src/System.Management.Automation/engine/interpreter/LocalAccess.cs +++ b/src/System.Management.Automation/engine/interpreter/LocalAccess.cs @@ -23,10 +23,13 @@ namespace System.Management.Automation.Interpreter { + +#nullable enable internal interface IBoxableInstruction { - Instruction BoxIfIndexMatches(int index); + Instruction? BoxIfIndexMatches(int index); } +#nullable restore internal abstract class LocalAccessInstruction : Instruction { diff --git a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs index 97908c8b2b6..0c1f6416741 100644 --- a/src/System.Management.Automation/engine/interpreter/LocalVariables.cs +++ b/src/System.Management.Automation/engine/interpreter/LocalVariables.cs @@ -35,7 +35,10 @@ internal sealed class LocalVariable public bool IsBoxed { - get { return (_flags & IsBoxedFlag) != 0; } + get + { + return (_flags & IsBoxedFlag) != 0; + } set { @@ -57,7 +60,7 @@ public bool InClosure public bool InClosureOrBoxed { - get { return InClosure | IsBoxed; } + get { return InClosure || IsBoxed; } } internal LocalVariable(int index, bool closure, bool boxed) @@ -78,7 +81,7 @@ public override string ToString() } } - internal struct LocalDefinition + internal readonly struct LocalDefinition { private readonly int _index; private readonly ParameterExpression _parameter; @@ -160,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); } @@ -293,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/MulInstruction.cs b/src/System.Management.Automation/engine/interpreter/MulInstruction.cs index 486d2ebc751..3b150a7abbd 100644 --- a/src/System.Management.Automation/engine/interpreter/MulInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/MulInstruction.cs @@ -130,14 +130,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new MulInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new MulInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new MulInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new MulUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new MulUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new MulUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new MulSingle()); - case TypeCode.Double: return s_double ?? (s_double = new MulDouble()); + case TypeCode.Int16: return s_int16 ??= new MulInt16(); + case TypeCode.Int32: return s_int32 ??= new MulInt32(); + case TypeCode.Int64: return s_int64 ??= new MulInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new MulUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new MulUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new MulUInt64(); + case TypeCode.Single: return s_single ??= new MulSingle(); + case TypeCode.Double: return s_double ??= new MulDouble(); default: throw Assert.Unreachable; @@ -263,14 +263,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new MulOvfInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new MulOvfInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new MulOvfInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new MulOvfUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new MulOvfUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new MulOvfUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new MulOvfSingle()); - case TypeCode.Double: return s_double ?? (s_double = new MulOvfDouble()); + case TypeCode.Int16: return s_int16 ??= new MulOvfInt16(); + case TypeCode.Int32: return s_int32 ??= new MulOvfInt32(); + case TypeCode.Int64: return s_int64 ??= new MulOvfInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new MulOvfUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new MulOvfUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new MulOvfUInt64(); + case TypeCode.Single: return s_single ??= new MulOvfSingle(); + case TypeCode.Double: return s_double ??= new MulOvfDouble(); default: throw Assert.Unreachable; diff --git a/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs b/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs index 42fc6639a82..79df1b9a84b 100644 --- a/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/NotEqualInstruction.cs @@ -154,25 +154,25 @@ public static Instruction Create(Type type) var typeToUse = type.IsEnum ? Enum.GetUnderlyingType(type) : type; switch (typeToUse.GetTypeCode()) { - case TypeCode.Boolean: return s_boolean ?? (s_boolean = new NotEqualBoolean()); - case TypeCode.SByte: return s_SByte ?? (s_SByte = new NotEqualSByte()); - case TypeCode.Byte: return s_byte ?? (s_byte = new NotEqualByte()); - case TypeCode.Char: return s_char ?? (s_char = new NotEqualChar()); - case TypeCode.Int16: return s_int16 ?? (s_int16 = new NotEqualInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new NotEqualInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new NotEqualInt64()); + case TypeCode.Boolean: return s_boolean ??= new NotEqualBoolean(); + case TypeCode.SByte: return s_SByte ??= new NotEqualSByte(); + case TypeCode.Byte: return s_byte ??= new NotEqualByte(); + case TypeCode.Char: return s_char ??= new NotEqualChar(); + case TypeCode.Int16: return s_int16 ??= new NotEqualInt16(); + case TypeCode.Int32: return s_int32 ??= new NotEqualInt32(); + case TypeCode.Int64: return s_int64 ??= new NotEqualInt64(); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new NotEqualInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new NotEqualInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new NotEqualInt64()); + case TypeCode.UInt16: return s_UInt16 ??= new NotEqualInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new NotEqualInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new NotEqualInt64(); - case TypeCode.Single: return s_single ?? (s_single = new NotEqualSingle()); - case TypeCode.Double: return s_double ?? (s_double = new NotEqualDouble()); + case TypeCode.Single: return s_single ??= new NotEqualSingle(); + case TypeCode.Double: return s_double ??= new NotEqualDouble(); case TypeCode.Object: if (!type.IsValueType) { - return s_reference ?? (s_reference = new NotEqualReference()); + return s_reference ??= new NotEqualReference(); } // TODO: Nullable throw new NotImplementedException(); @@ -188,4 +188,3 @@ public override string ToString() } } } - 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/SubInstruction.cs b/src/System.Management.Automation/engine/interpreter/SubInstruction.cs index c0874d47d89..486cd7f8933 100644 --- a/src/System.Management.Automation/engine/interpreter/SubInstruction.cs +++ b/src/System.Management.Automation/engine/interpreter/SubInstruction.cs @@ -130,14 +130,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new SubInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new SubInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new SubInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new SubUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new SubUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new SubUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new SubSingle()); - case TypeCode.Double: return s_double ?? (s_double = new SubDouble()); + case TypeCode.Int16: return s_int16 ??= new SubInt16(); + case TypeCode.Int32: return s_int32 ??= new SubInt32(); + case TypeCode.Int64: return s_int64 ??= new SubInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new SubUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new SubUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new SubUInt64(); + case TypeCode.Single: return s_single ??= new SubSingle(); + case TypeCode.Double: return s_double ??= new SubDouble(); default: throw Assert.Unreachable; @@ -263,14 +263,14 @@ public static Instruction Create(Type type) Debug.Assert(!type.IsEnum); switch (type.GetTypeCode()) { - case TypeCode.Int16: return s_int16 ?? (s_int16 = new SubOvfInt16()); - case TypeCode.Int32: return s_int32 ?? (s_int32 = new SubOvfInt32()); - case TypeCode.Int64: return s_int64 ?? (s_int64 = new SubOvfInt64()); - case TypeCode.UInt16: return s_UInt16 ?? (s_UInt16 = new SubOvfUInt16()); - case TypeCode.UInt32: return s_UInt32 ?? (s_UInt32 = new SubOvfUInt32()); - case TypeCode.UInt64: return s_UInt64 ?? (s_UInt64 = new SubOvfUInt64()); - case TypeCode.Single: return s_single ?? (s_single = new SubOvfSingle()); - case TypeCode.Double: return s_double ?? (s_double = new SubOvfDouble()); + case TypeCode.Int16: return s_int16 ??= new SubOvfInt16(); + case TypeCode.Int32: return s_int32 ??= new SubOvfInt32(); + case TypeCode.Int64: return s_int64 ??= new SubOvfInt64(); + case TypeCode.UInt16: return s_UInt16 ??= new SubOvfUInt16(); + case TypeCode.UInt32: return s_UInt32 ??= new SubOvfUInt32(); + case TypeCode.UInt64: return s_UInt64 ??= new SubOvfUInt64(); + case TypeCode.Single: return s_single ??= new SubOvfSingle(); + case TypeCode.Double: return s_double ??= new SubOvfDouble(); default: throw Assert.Unreachable; diff --git a/src/System.Management.Automation/engine/interpreter/Utilities.cs b/src/System.Management.Automation/engine/interpreter/Utilities.cs index 810dea30d1a..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); @@ -224,7 +224,7 @@ internal static Type MakeDelegate(Type[] types) } } - internal class ScriptingRuntimeHelpers + internal static class ScriptingRuntimeHelpers { internal static object Int32ToObject(int i) { @@ -668,7 +668,7 @@ public TValue this[TKey key] } } - private struct KeyInfo + private readonly struct KeyInfo { internal readonly TValue Value; internal readonly LinkedListNode List; @@ -779,7 +779,7 @@ public StorageInfo GetStorageInfo() private StorageInfo GetStorageInfo(StorageInfo[] curStorage) { - int threadId = Thread.CurrentThread.ManagedThreadId; + int threadId = Environment.CurrentManagedThreadId; // fast path if we already have a value in the array if (curStorage != null && curStorage.Length > threadId) @@ -829,7 +829,7 @@ private StorageInfo CreateStorageInfo() StorageInfo[] curStorage = s_updating; try { - int threadId = Thread.CurrentThread.ManagedThreadId; + int threadId = Environment.CurrentManagedThreadId; StorageInfo newInfo = new StorageInfo(Thread.CurrentThread); // set to updating while potentially resizing/mutating, then we'll @@ -1078,20 +1078,23 @@ 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; } - internal static U[] Map(this ICollection collection, Func select) + internal static TResult[] Map(this ICollection source, Func selector) { - int count = collection.Count; - U[] result = new U[count]; + int count = source.Count; + TResult[] result = new TResult[count]; count = 0; - foreach (T t in collection) + foreach (TSource t in source) { - result[count++] = select(t); + result[count++] = selector(t); } return result; diff --git a/src/System.Management.Automation/engine/lang/interface/PSParseError.cs b/src/System.Management.Automation/engine/lang/interface/PSParseError.cs index 5fa12cc073c..00a273cc6c0 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSParseError.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSParseError.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + /********************************************************************++ Project: PowerShell diff --git a/src/System.Management.Automation/engine/lang/interface/PSParser.cs b/src/System.Management.Automation/engine/lang/interface/PSParser.cs index 2b6886dbce6..ade092e18ad 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSParser.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSParser.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + /********************************************************************++ Project: PowerShell diff --git a/src/System.Management.Automation/engine/lang/interface/PSToken.cs b/src/System.Management.Automation/engine/lang/interface/PSToken.cs index b0b34ec0544..306a64a0b61 100644 --- a/src/System.Management.Automation/engine/lang/interface/PSToken.cs +++ b/src/System.Management.Automation/engine/lang/interface/PSToken.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + /********************************************************************++ Project: PowerShell @@ -68,7 +69,7 @@ public string Content } } - private string _content; + private readonly string _content; #region Token Type @@ -299,7 +300,9 @@ public static PSTokenType GetPSTokenType(Token token) /* Type */ PSTokenType.Keyword, /* Assembly */ PSTokenType.Keyword, /* Command */ PSTokenType.Keyword, - /* Def */ PSTokenType.Keyword, + /* Hidden */ PSTokenType.Keyword, + /* Base */ PSTokenType.Keyword, + /* Default */ PSTokenType.Keyword, #endregion Flags for keywords @@ -367,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 d3af98dcea5..184c82c6815 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) - { - } } /// @@ -190,7 +164,7 @@ public StopUpstreamCommandsException(InternalCommand requestingCommand) this.RequestingCommandProcessor = requestingCommand.Context.CurrentCommandProcessor; } - public CommandProcessorBase RequestingCommandProcessor { get; private set; } + public CommandProcessorBase RequestingCommandProcessor { get; } } #endregion Flow Control Exceptions @@ -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. /// @@ -378,8 +352,8 @@ internal static object ImplicitOp(object lval, object rval, string op, IScriptEx lval = PSObject.Base(lval); rval = PSObject.Base(rval); - Type lvalType = lval != null ? lval.GetType() : null; - Type rvalType = rval != null ? rval.GetType() : null; + Type lvalType = lval?.GetType(); + Type rvalType = rval?.GetType(); Type opType; if (lvalType == null || (lvalType.IsPrimitive)) { @@ -965,20 +939,13 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e } } + var replacer = ReplaceOperatorImpl.Create(context, rr, substitute); 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 ReplaceOperatorImpl(context, lvalString, rr, substitute); + return replacer.Replace(lvalString); } else { @@ -986,51 +953,84 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e while (ParserOps.MoveNext(context, errorPosition, list)) { string lvalString = PSObject.ToStringParser(context, ParserOps.Current(errorPosition, list)); - resultList.Add(ReplaceOperatorImpl(context, lvalString, rr, substitute)); + resultList.Add(replacer.Replace(lvalString)); } return resultList.ToArray(); } } - /// - /// ReplaceOperator implementation. - /// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate - /// and finally returns the result of the final Regex.Replace operation. - /// - /// The execution context in which to evaluate the expression. - /// The input string. - /// A Regex instance. - /// The substitute value. - /// The result of the regex.Replace operation. - private static object ReplaceOperatorImpl(ExecutionContext context, string input, Regex regex, object substitute) + private struct ReplaceOperatorImpl { - switch (substitute) + public static ReplaceOperatorImpl Create(ExecutionContext context, Regex regex, object substitute) + { + return new ReplaceOperatorImpl(context, regex, substitute); + } + + private readonly Regex _regex; + private readonly string _cachedReplacementString; + private readonly MatchEvaluator _cachedMatchEvaluator; + + private ReplaceOperatorImpl( + ExecutionContext context, + Regex regex, + object substitute) { - case string replacementString: - return regex.Replace(input, replacementString); + _regex = regex; + _cachedReplacementString = null; + _cachedMatchEvaluator = null; - case ScriptBlock sb: - MatchEvaluator me = match => - { - var result = sb.DoInvokeReturnAsIs( - useLocalScope: false, /* Use current scope to be consistent with 'ForEach/Where-Object {}' and 'collection.ForEach{}/Where{}' */ - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: match, - input: AutomationNull.Value, - scriptThis: AutomationNull.Value, - args: Array.Empty()); + switch (substitute) + { + case string replacement: + _cachedReplacementString = replacement; + break; + + case ScriptBlock sb: + _cachedMatchEvaluator = GetMatchEvaluator(context, sb); + break; - return PSObject.ToStringParser(context, result); - }; - return regex.Replace(input, me); + case object val when LanguagePrimitives.TryConvertTo(val, out _cachedMatchEvaluator): + break; + + default: + _cachedReplacementString = PSObject.ToStringParser(context, substitute); + break; + } + } - case object val when LanguagePrimitives.TryConvertTo(val, out MatchEvaluator matchEvaluator): - return regex.Replace(input, matchEvaluator); + // Local helper function to avoid creating an instance of the generated delegate helper class + // every time 'ReplaceOperatorImpl' is invoked. + private static MatchEvaluator GetMatchEvaluator(ExecutionContext context, ScriptBlock sb) + { + return match => + { + var result = sb.DoInvokeReturnAsIs( + useLocalScope: false, /* Use current scope to be consistent with 'ForEach/Where-Object {}' and 'collection.ForEach{}/Where{}' */ + errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, + dollarUnder: match, + input: AutomationNull.Value, + scriptThis: AutomationNull.Value, + args: Array.Empty()); - default: - string replacement = PSObject.ToStringParser(context, substitute); - return regex.Replace(input, replacement); + return PSObject.ToStringParser(context, result); + }; + } + + /// + /// ReplaceOperator implementation. + /// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate + /// and finally returns the result of the final Regex.Replace operation. + /// + public object Replace(string input) + { + if (_cachedReplacementString is not null) + { + return _regex.Replace(input, _cachedReplacementString); + } + + Dbg.Assert(_cachedMatchEvaluator is not null, "_cachedMatchEvaluator should be not null when code reach here."); + return _regex.Replace(input, _cachedMatchEvaluator); } } @@ -1466,8 +1466,7 @@ internal static string GetTypeFullName(object obj) return string.Empty; } - PSObject mshObj = obj as PSObject; - if (mshObj == null) + if (obj is not PSObject mshObj) { return obj.GetType().FullName; } @@ -1485,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. @@ -1571,9 +1570,7 @@ internal static object CallMethod( // not really a method call. if (valueToSet != AutomationNull.Value) { - PSParameterizedProperty propertyToSet = targetMethod as PSParameterizedProperty; - - if (propertyToSet == null) + if (targetMethod is not PSParameterizedProperty propertyToSet) { throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), errorPosition, "ParameterizedPropertyAssignmentFailed", ParserStrings.ParameterizedPropertyAssignmentFailed, GetTypeFullName(target), methodName); @@ -1635,14 +1632,14 @@ internal static object CallMethod( /// internal class RangeEnumerator : IEnumerator { - private int _lowerBound; + private readonly int _lowerBound; internal int LowerBound { get { return _lowerBound; } } - private int _upperBound; + private readonly int _upperBound; internal int UpperBound { @@ -1666,7 +1663,7 @@ internal int CurrentValue get { return _current; } } - private int _increment = 1; + private readonly int _increment = 1; private bool _firstElement = true; @@ -1707,7 +1704,7 @@ public bool MoveNext() /// internal class CharRangeEnumerator : IEnumerator { - private int _increment = 1; + private readonly int _increment = 1; private bool _firstElement = true; @@ -1725,15 +1722,9 @@ object IEnumerator.Current get { return Current; } } - internal char LowerBound - { - get; private set; - } + internal char LowerBound { get; } - internal char UpperBound - { - get; private set; - } + internal char UpperBound { get; } public char Current { @@ -1818,7 +1809,7 @@ internal static RuntimeException NewInterpreterExceptionWithInnerException(objec try { string message; - if (args == null || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; @@ -1956,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 @@ -1979,7 +1986,7 @@ internal static void Trace(ExecutionContext context, int level, string messageId if (context.PSDebugTraceLevel > level) { string message; - if (args == null || 0 == args.Length) + if (args == null || args.Length == 0) { // Don't format in case the string contains literal curly braces message = resourceString; diff --git a/src/System.Management.Automation/engine/lang/scriptblock.cs b/src/System.Management.Automation/engine/lang/scriptblock.cs index 5930273ee60..06fa66d0f7c 100644 --- a/src/System.Management.Automation/engine/lang/scriptblock.cs +++ b/src/System.Management.Automation/engine/lang/scriptblock.cs @@ -104,7 +104,7 @@ internal static ScriptBlock Create(ExecutionContext context, string script) /// /// The string to compile. public static ScriptBlock Create(string script) => Create( - parser: new Language.Parser(), + parser: new Parser(), fileName: null, fileContents: script); @@ -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); @@ -596,7 +596,7 @@ internal void InvokeAsMemberFunction(object instance, object[] args) /// Get the PSModuleInfo object for the module that defined this /// scriptblock. /// - public PSModuleInfo Module { get => SessionStateInternal != null ? SessionStateInternal.Module : null; } + public PSModuleInfo Module { get => SessionStateInternal?.Module; } /// /// Return the PSToken object for this function definition... @@ -709,7 +709,7 @@ internal SessionState SessionState } } - return SessionStateInternal != null ? SessionStateInternal.PublicSessionState : null; + return SessionStateInternal?.PublicSessionState; } set @@ -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,46 +1096,37 @@ 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; } - private PipelineProcessor _pipeline; - private ExecutionContext _context; + private readonly PipelineProcessor _pipeline; + private readonly ExecutionContext _context; private bool _expectInput; /// /// Begin execution of a steppable pipeline. This overload doesn't reroute output and error pipes. /// - /// true if you plan to write input into this pipe; false otherwise. + /// if you plan to write input into this pipe; otherwise. public void Begin(bool expectInput) => Begin(expectInput, commandRuntime: (ICommandRuntime)null); /// /// Begin execution of a steppable pipeline, using the command running currently in the specified context to figure /// out how to route the output and errors. /// - /// true if you plan to write input into this pipe; false otherwise. + /// if you plan to write input into this pipe; otherwise. /// 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; - ICommandRuntime crt = commandProcessor == null ? null : commandProcessor.CommandRuntime; + ICommandRuntime crt = commandProcessor?.CommandRuntime; Begin(expectInput, crt); } @@ -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,34 +1318,16 @@ 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; } - /// - /// Finalizer for class SteppablePipeline. - /// - ~SteppablePipeline() - { - Dispose(false); - } - #endregion IDispose } @@ -1328,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 @@ -1355,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) { @@ -1382,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 @@ -1447,13 +1454,21 @@ internal ScriptBlockInvocationEventArgs( } internal ScriptBlock ScriptBlock { get; set; } + internal bool UseLocalScope { get; set; } + internal ScriptBlock.ErrorHandlingBehavior ErrorHandlingBehavior { get; set; } + internal object DollarUnder { get; set; } + internal object Input { get; set; } + internal object ScriptThis { get; set; } + internal Pipe OutputPipe { get; set; } + internal InvocationInfo InvocationInfo { get; set; } + internal object[] Args { get; set; } /// diff --git a/src/System.Management.Automation/engine/parser/AstVisitor.cs b/src/System.Management.Automation/engine/parser/AstVisitor.cs index 03ce04ea4ad..03b234cf41f 100644 --- a/src/System.Management.Automation/engine/parser/AstVisitor.cs +++ b/src/System.Management.Automation/engine/parser/AstVisitor.cs @@ -10,175 +10,230 @@ namespace System.Management.Automation.Language { /// /// +#nullable enable public interface ICustomAstVisitor { /// - object VisitErrorStatement(ErrorStatementAst errorStatementAst); + object? DefaultVisit(Ast ast) => null; + + /// + object? VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + /// - object VisitErrorExpression(ErrorExpressionAst errorExpressionAst); + object? VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); #region Script Blocks /// - object VisitScriptBlock(ScriptBlockAst 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); + object? VisitParamBlock(ParamBlockAst paramBlockAst) => DefaultVisit(paramBlockAst); + /// - object VisitNamedBlock(NamedBlockAst namedBlockAst); + object? VisitNamedBlock(NamedBlockAst namedBlockAst) => DefaultVisit(namedBlockAst); + /// - object VisitTypeConstraint(TypeConstraintAst typeConstraintAst); + object? VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => DefaultVisit(typeConstraintAst); + /// - object VisitAttribute(AttributeAst attributeAst); + object? VisitAttribute(AttributeAst attributeAst) => DefaultVisit(attributeAst); + /// - object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst); + object? VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => DefaultVisit(namedAttributeArgumentAst); + /// - object VisitParameter(ParameterAst parameterAst); + object? VisitParameter(ParameterAst parameterAst) => DefaultVisit(parameterAst); #endregion Script Blocks #region Statements /// - object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst); + object? VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => DefaultVisit(functionDefinitionAst); + /// - object VisitStatementBlock(StatementBlockAst statementBlockAst); + object? VisitStatementBlock(StatementBlockAst statementBlockAst) => DefaultVisit(statementBlockAst); + /// - object VisitIfStatement(IfStatementAst ifStmtAst); + object? VisitIfStatement(IfStatementAst ifStmtAst) => DefaultVisit(ifStmtAst); + /// - object VisitTrap(TrapStatementAst trapStatementAst); + object? VisitTrap(TrapStatementAst trapStatementAst) => DefaultVisit(trapStatementAst); + /// - object VisitSwitchStatement(SwitchStatementAst switchStatementAst); + object? VisitSwitchStatement(SwitchStatementAst switchStatementAst) => DefaultVisit(switchStatementAst); + /// - object VisitDataStatement(DataStatementAst dataStatementAst); + object? VisitDataStatement(DataStatementAst dataStatementAst) => DefaultVisit(dataStatementAst); + /// - object VisitForEachStatement(ForEachStatementAst forEachStatementAst); + object? VisitForEachStatement(ForEachStatementAst forEachStatementAst) => DefaultVisit(forEachStatementAst); + /// - object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst); + object? VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => DefaultVisit(doWhileStatementAst); + /// - object VisitForStatement(ForStatementAst forStatementAst); + object? VisitForStatement(ForStatementAst forStatementAst) => DefaultVisit(forStatementAst); + /// - object VisitWhileStatement(WhileStatementAst whileStatementAst); + object? VisitWhileStatement(WhileStatementAst whileStatementAst) => DefaultVisit(whileStatementAst); + /// - object VisitCatchClause(CatchClauseAst catchClauseAst); + object? VisitCatchClause(CatchClauseAst catchClauseAst) => DefaultVisit(catchClauseAst); + /// - object VisitTryStatement(TryStatementAst tryStatementAst); + object? VisitTryStatement(TryStatementAst tryStatementAst) => DefaultVisit(tryStatementAst); + /// - object VisitBreakStatement(BreakStatementAst breakStatementAst); + object? VisitBreakStatement(BreakStatementAst breakStatementAst) => DefaultVisit(breakStatementAst); + /// - object VisitContinueStatement(ContinueStatementAst continueStatementAst); + object? VisitContinueStatement(ContinueStatementAst continueStatementAst) => DefaultVisit(continueStatementAst); + /// - object VisitReturnStatement(ReturnStatementAst returnStatementAst); + object? VisitReturnStatement(ReturnStatementAst returnStatementAst) => DefaultVisit(returnStatementAst); + /// - object VisitExitStatement(ExitStatementAst exitStatementAst); + object? VisitExitStatement(ExitStatementAst exitStatementAst) => DefaultVisit(exitStatementAst); + /// - object VisitThrowStatement(ThrowStatementAst throwStatementAst); + object? VisitThrowStatement(ThrowStatementAst throwStatementAst) => DefaultVisit(throwStatementAst); + /// - object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst); + object? VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => DefaultVisit(doUntilStatementAst); + /// - object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst); + object? VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) => DefaultVisit(assignmentStatementAst); #endregion Statements #region Pipelines /// - object VisitPipeline(PipelineAst pipelineAst); + object? VisitPipeline(PipelineAst pipelineAst) => DefaultVisit(pipelineAst); + /// - object VisitCommand(CommandAst commandAst); + object? VisitCommand(CommandAst commandAst) => DefaultVisit(commandAst); + /// - object VisitCommandExpression(CommandExpressionAst commandExpressionAst); + object? VisitCommandExpression(CommandExpressionAst commandExpressionAst) => DefaultVisit(commandExpressionAst); + /// - object VisitCommandParameter(CommandParameterAst commandParameterAst); + object? VisitCommandParameter(CommandParameterAst commandParameterAst) => DefaultVisit(commandParameterAst); + /// - object VisitFileRedirection(FileRedirectionAst fileRedirectionAst); + object? VisitFileRedirection(FileRedirectionAst fileRedirectionAst) => DefaultVisit(fileRedirectionAst); + /// - object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst); + object? VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) => DefaultVisit(mergingRedirectionAst); + #endregion Pipelines #region Expressions /// - object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst); + object? VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => DefaultVisit(binaryExpressionAst); + /// - object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst); + object? VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => DefaultVisit(unaryExpressionAst); + /// - object VisitConvertExpression(ConvertExpressionAst convertExpressionAst); + object? VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => DefaultVisit(convertExpressionAst); + /// - object VisitConstantExpression(ConstantExpressionAst constantExpressionAst); + object? VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => DefaultVisit(constantExpressionAst); + /// - object VisitStringConstantExpression(StringConstantExpressionAst 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); + object? VisitSubExpression(SubExpressionAst subExpressionAst) => DefaultVisit(subExpressionAst); + /// - object VisitUsingExpression(UsingExpressionAst usingExpressionAst); + object? VisitUsingExpression(UsingExpressionAst usingExpressionAst) => DefaultVisit(usingExpressionAst); + /// - object VisitVariableExpression(VariableExpressionAst variableExpressionAst); + object? VisitVariableExpression(VariableExpressionAst variableExpressionAst) => DefaultVisit(variableExpressionAst); + /// - object VisitTypeExpression(TypeExpressionAst typeExpressionAst); + object? VisitTypeExpression(TypeExpressionAst typeExpressionAst) => DefaultVisit(typeExpressionAst); + /// - object VisitMemberExpression(MemberExpressionAst memberExpressionAst); + object? VisitMemberExpression(MemberExpressionAst memberExpressionAst) => DefaultVisit(memberExpressionAst); + /// - object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst); + object? VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) => DefaultVisit(invokeMemberExpressionAst); + /// - object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst); + object? VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => DefaultVisit(arrayExpressionAst); + /// - object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst); + object? VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => DefaultVisit(arrayLiteralAst); + /// - object VisitHashtable(HashtableAst hashtableAst); + object? VisitHashtable(HashtableAst hashtableAst) => DefaultVisit(hashtableAst); + /// - object VisitScriptBlockExpression(ScriptBlockExpressionAst 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); + object? VisitParenExpression(ParenExpressionAst parenExpressionAst) => DefaultVisit(parenExpressionAst); + /// - object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst); + object? VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => DefaultVisit(expandableStringExpressionAst); + /// - object VisitIndexExpression(IndexExpressionAst indexExpressionAst); + object? VisitIndexExpression(IndexExpressionAst indexExpressionAst) => DefaultVisit(indexExpressionAst); + /// - object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst); + object? VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => DefaultVisit(attributedExpressionAst); + /// - object VisitBlockStatement(BlockStatementAst blockStatementAst); + object? VisitBlockStatement(BlockStatementAst blockStatementAst) => DefaultVisit(blockStatementAst); #endregion Expressions } +#nullable restore /// +#nullable enable public interface ICustomAstVisitor2 : ICustomAstVisitor { - private object DefaultVisit(Ast ast) => null; - /// - object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst); + object? VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => DefaultVisit(typeDefinitionAst); /// - object VisitPropertyMember(PropertyMemberAst propertyMemberAst); + object? VisitPropertyMember(PropertyMemberAst propertyMemberAst) => DefaultVisit(propertyMemberAst); /// - object VisitFunctionMember(FunctionMemberAst functionMemberAst); + object? VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); /// - object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst); + object? VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => DefaultVisit(baseCtorInvokeMemberExpressionAst); /// - object VisitUsingStatement(UsingStatementAst usingStatement); + object? VisitUsingStatement(UsingStatementAst usingStatement) => DefaultVisit(usingStatement); /// - object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst); + object? VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => DefaultVisit(configurationDefinitionAst); /// - object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst); + object? VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) => DefaultVisit(dynamicKeywordAst); /// - object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); + object? VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); /// - object VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst); + object? VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst); } +#nullable restore #if DEBUG internal class CheckAllParentsSet : AstVisitor2 @@ -188,7 +243,7 @@ internal CheckAllParentsSet(Ast root) this.Root = root; } - private Ast Root { get; set; } + private Ast Root { get; } internal AstVisitAction CheckParent(Ast ast) { @@ -335,7 +390,7 @@ public override AstVisitAction VisitTypeConstraint(TypeConstraintAst ast) Type type = ast.TypeName.GetReflectionType(); if (type != null) { - Diagnostics.Assert(!(type is TypeBuilder), "ReflectionType can never be TypeBuilder"); + Diagnostics.Assert(type is not TypeBuilder, "ReflectionType can never be TypeBuilder"); } return AstVisitAction.Continue; @@ -374,7 +429,7 @@ internal static bool Contains(Ast ast, Func predicate, bool searchNes var searcher = new AstSearcher(predicate, stopOnFirst: true, searchNestedScriptBlocks: searchNestedScriptBlocks); ast.InternalVisit(searcher); - return searcher.Results.Any(); + return searcher.Results.Count > 0; } internal static bool IsUsingDollarInput(Ast ast) @@ -567,113 +622,169 @@ protected AstVisitAction CheckScriptBlock(Ast ast) public abstract class DefaultCustomAstVisitor : ICustomAstVisitor { /// - public virtual object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return null; } + public virtual object DefaultVisit(Ast ast) => null; + /// - public virtual object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return null; } + public virtual object VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + /// - public virtual object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return null; } + public virtual object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); + + /// + public virtual object VisitScriptBlock(ScriptBlockAst scriptBlockAst) => DefaultVisit(scriptBlockAst); + /// - public virtual object VisitParamBlock(ParamBlockAst paramBlockAst) { return null; } + public virtual object VisitParamBlock(ParamBlockAst paramBlockAst) => DefaultVisit(paramBlockAst); + /// - public virtual object VisitNamedBlock(NamedBlockAst namedBlockAst) { return null; } + public virtual object VisitNamedBlock(NamedBlockAst namedBlockAst) => DefaultVisit(namedBlockAst); + /// - public virtual object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return null; } + public virtual object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => DefaultVisit(typeConstraintAst); + /// - public virtual object VisitAttribute(AttributeAst attributeAst) { return null; } + public virtual object VisitAttribute(AttributeAst attributeAst) => DefaultVisit(attributeAst); + /// - public virtual object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return null; } + public virtual object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => DefaultVisit(namedAttributeArgumentAst); + /// - public virtual object VisitParameter(ParameterAst parameterAst) { return null; } + public virtual object VisitParameter(ParameterAst parameterAst) => DefaultVisit(parameterAst); + /// - public virtual object VisitStatementBlock(StatementBlockAst statementBlockAst) { return null; } + public virtual object VisitStatementBlock(StatementBlockAst statementBlockAst) => DefaultVisit(statementBlockAst); + /// - public virtual object VisitIfStatement(IfStatementAst ifStmtAst) { return null; } + public virtual object VisitIfStatement(IfStatementAst ifStmtAst) => DefaultVisit(ifStmtAst); + /// - public virtual object VisitTrap(TrapStatementAst trapStatementAst) { return null; } + public virtual object VisitTrap(TrapStatementAst trapStatementAst) => DefaultVisit(trapStatementAst); + /// - public virtual object VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return null; } + public virtual object VisitSwitchStatement(SwitchStatementAst switchStatementAst) => DefaultVisit(switchStatementAst); + /// - public virtual object VisitDataStatement(DataStatementAst dataStatementAst) { return null; } + public virtual object VisitDataStatement(DataStatementAst dataStatementAst) => DefaultVisit(dataStatementAst); + /// - public virtual object VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return null; } + public virtual object VisitForEachStatement(ForEachStatementAst forEachStatementAst) => DefaultVisit(forEachStatementAst); + /// - public virtual object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return null; } + public virtual object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => DefaultVisit(doWhileStatementAst); + /// - public virtual object VisitForStatement(ForStatementAst forStatementAst) { return null; } + public virtual object VisitForStatement(ForStatementAst forStatementAst) => DefaultVisit(forStatementAst); + /// - public virtual object VisitWhileStatement(WhileStatementAst whileStatementAst) { return null; } + public virtual object VisitWhileStatement(WhileStatementAst whileStatementAst) => DefaultVisit(whileStatementAst); + /// - public virtual object VisitCatchClause(CatchClauseAst catchClauseAst) { return null; } + public virtual object VisitCatchClause(CatchClauseAst catchClauseAst) => DefaultVisit(catchClauseAst); + /// - public virtual object VisitTryStatement(TryStatementAst tryStatementAst) { return null; } + public virtual object VisitTryStatement(TryStatementAst tryStatementAst) => DefaultVisit(tryStatementAst); + /// - public virtual object VisitBreakStatement(BreakStatementAst breakStatementAst) { return null; } + public virtual object VisitBreakStatement(BreakStatementAst breakStatementAst) => DefaultVisit(breakStatementAst); + /// - public virtual object VisitContinueStatement(ContinueStatementAst continueStatementAst) { return null; } + public virtual object VisitContinueStatement(ContinueStatementAst continueStatementAst) => DefaultVisit(continueStatementAst); + /// - public virtual object VisitReturnStatement(ReturnStatementAst returnStatementAst) { return null; } + public virtual object VisitReturnStatement(ReturnStatementAst returnStatementAst) => DefaultVisit(returnStatementAst); + /// - public virtual object VisitExitStatement(ExitStatementAst exitStatementAst) { return null; } + public virtual object VisitExitStatement(ExitStatementAst exitStatementAst) => DefaultVisit(exitStatementAst); + /// - public virtual object VisitThrowStatement(ThrowStatementAst throwStatementAst) { return null; } + public virtual object VisitThrowStatement(ThrowStatementAst throwStatementAst) => DefaultVisit(throwStatementAst); + /// - public virtual object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return null; } + public virtual object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => DefaultVisit(doUntilStatementAst); + /// - public virtual object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return null; } + public virtual object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) => DefaultVisit(assignmentStatementAst); + /// - public virtual object VisitPipeline(PipelineAst pipelineAst) { return null; } + public virtual object VisitPipeline(PipelineAst pipelineAst) => DefaultVisit(pipelineAst); + /// - public virtual object VisitCommand(CommandAst commandAst) { return null; } + public virtual object VisitCommand(CommandAst commandAst) => DefaultVisit(commandAst); + /// - public virtual object VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return null; } + public virtual object VisitCommandExpression(CommandExpressionAst commandExpressionAst) => DefaultVisit(commandExpressionAst); + /// - public virtual object VisitCommandParameter(CommandParameterAst commandParameterAst) { return null; } + public virtual object VisitCommandParameter(CommandParameterAst commandParameterAst) => DefaultVisit(commandParameterAst); + /// - public virtual object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) { return null; } + public virtual object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) => DefaultVisit(fileRedirectionAst); + /// - public virtual object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) { return null; } + public virtual object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) => DefaultVisit(mergingRedirectionAst); + /// - public virtual object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return null; } + public virtual object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => DefaultVisit(binaryExpressionAst); + /// - public virtual object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { return null; } + public virtual object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => DefaultVisit(unaryExpressionAst); + /// - public virtual object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { return null; } + public virtual object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => DefaultVisit(convertExpressionAst); + /// - public virtual object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) { return null; } + public virtual object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => DefaultVisit(constantExpressionAst); + /// - public virtual object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) { return null; } + public virtual object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) => DefaultVisit(stringConstantExpressionAst); + /// - public virtual object VisitSubExpression(SubExpressionAst subExpressionAst) { return null; } + public virtual object VisitSubExpression(SubExpressionAst subExpressionAst) => DefaultVisit(subExpressionAst); + /// - public virtual object VisitUsingExpression(UsingExpressionAst usingExpressionAst) { return null; } + public virtual object VisitUsingExpression(UsingExpressionAst usingExpressionAst) => DefaultVisit(usingExpressionAst); + /// - public virtual object VisitVariableExpression(VariableExpressionAst variableExpressionAst) { return null; } + public virtual object VisitVariableExpression(VariableExpressionAst variableExpressionAst) => DefaultVisit(variableExpressionAst); + /// - public virtual object VisitTypeExpression(TypeExpressionAst typeExpressionAst) { return null; } + public virtual object VisitTypeExpression(TypeExpressionAst typeExpressionAst) => DefaultVisit(typeExpressionAst); + /// - public virtual object VisitMemberExpression(MemberExpressionAst memberExpressionAst) { return null; } + public virtual object VisitMemberExpression(MemberExpressionAst memberExpressionAst) => DefaultVisit(memberExpressionAst); + /// - public virtual object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) { return null; } + public virtual object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) => DefaultVisit(invokeMemberExpressionAst); + /// - public virtual object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) { return null; } + public virtual object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => DefaultVisit(arrayExpressionAst); + /// - public virtual object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) { return null; } + public virtual object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => DefaultVisit(arrayLiteralAst); + /// - public virtual object VisitHashtable(HashtableAst hashtableAst) { return null; } + public virtual object VisitHashtable(HashtableAst hashtableAst) => DefaultVisit(hashtableAst); + /// - public virtual object VisitParenExpression(ParenExpressionAst parenExpressionAst) { return null; } + public virtual object VisitParenExpression(ParenExpressionAst parenExpressionAst) => DefaultVisit(parenExpressionAst); + /// - public virtual object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return null; } + public virtual object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => DefaultVisit(expandableStringExpressionAst); + /// - public virtual object VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return null; } + public virtual object VisitIndexExpression(IndexExpressionAst indexExpressionAst) => DefaultVisit(indexExpressionAst); + /// - public virtual object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return null; } + public virtual object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => DefaultVisit(attributedExpressionAst); + /// - public virtual object VisitBlockStatement(BlockStatementAst blockStatementAst) { return null; } + public virtual object VisitBlockStatement(BlockStatementAst blockStatementAst) => DefaultVisit(blockStatementAst); + /// - public virtual object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return null; } + public virtual object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => DefaultVisit(functionDefinitionAst); + /// - public virtual object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) { return null; } + public virtual object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) => DefaultVisit(scriptBlockExpressionAst); } /// @@ -682,22 +793,30 @@ public abstract class DefaultCustomAstVisitor : ICustomAstVisitor public abstract class DefaultCustomAstVisitor2 : DefaultCustomAstVisitor, ICustomAstVisitor2 { /// - public virtual object VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return null; } + public virtual object VisitPropertyMember(PropertyMemberAst propertyMemberAst) => DefaultVisit(propertyMemberAst); + /// - public virtual object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return null; } + public virtual object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => DefaultVisit(baseCtorInvokeMemberExpressionAst); + /// - public virtual object VisitUsingStatement(UsingStatementAst usingStatement) { return null; } + public virtual object VisitUsingStatement(UsingStatementAst usingStatement) => DefaultVisit(usingStatement); + /// - public virtual object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationAst) { return null; } + public virtual object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationAst) => DefaultVisit(configurationAst); + /// - public virtual object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) { return null; } + public virtual object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst) => DefaultVisit(dynamicKeywordAst); + /// - public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return null; } + public virtual object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => DefaultVisit(typeDefinitionAst); + /// - public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) { return null; } + public virtual object VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); + /// - public virtual object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return null; } + public virtual object VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); + /// - public virtual object VisitPipelineChain(PipelineChainAst statementChainAst) { return null; } + public virtual object VisitPipelineChain(PipelineChainAst statementChainAst) => DefaultVisit(statementChainAst); } } diff --git a/src/System.Management.Automation/engine/parser/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index e77097f2088..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"); @@ -926,7 +945,10 @@ internal Type MemberFunctionReturnType return _memberFunctionReturnType; } - set { _memberFunctionReturnType = value; } + set + { + _memberFunctionReturnType = value; + } } private Type _memberFunctionReturnType; @@ -949,9 +971,20 @@ internal Expression CompileExpressionOperand(ExpressionAst exprAst) return result; } - private IEnumerable CompileInvocationArguments(IEnumerable arguments) + private IEnumerable CompileInvocationArguments(IReadOnlyList arguments) { - return arguments == null ? Array.Empty() : arguments.Select(CompileExpressionOperand); + if (arguments is null || arguments.Count == 0) + { + return Array.Empty(); + } + + var result = new Expression[arguments.Count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = CompileExpressionOperand(arguments[i]); + } + + return result; } internal Expression ReduceAssignment(ISupportsAssignment left, TokenKind tokenKind, Expression right) @@ -1045,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 @@ -1055,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. @@ -1091,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, @@ -1133,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) @@ -1155,12 +1185,12 @@ internal static Type GetTypeConstraintForMethodResolution(ExpressionAst expr) expr = ((AttributedExpressionAst)expr).Child; } - return firstConvert == null ? null : firstConvert.Type.TypeName.GetReflectionType(); + return firstConvert?.Type.TypeName.GetReflectionType(); } internal static PSMethodInvocationConstraints CombineTypeConstraintForMethodResolution(Type targetType, Type argType) { - if (targetType == null && argType == null) + if (targetType is null && argType is null) { return null; } @@ -1168,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) @@ -1243,8 +1278,8 @@ 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); - if (!(rdp.ParameterType == typeof(SwitchParameter))) + var paramAttribute = (ParameterAttribute)rdp.Attributes.First(static attr => attr is ParameterAttribute); + if (rdp.ParameterType != typeof(SwitchParameter)) { paramAttribute.Position = pos++; } @@ -1544,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 { @@ -1705,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); @@ -1734,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; @@ -2012,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; @@ -2022,19 +2063,17 @@ 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 Action CompileTree(Expression> lambda, CompileInterpretChoice compileInterpretChoice) + private static Action CompileTree(Expression> lambda, CompileInterpretChoice compileInterpretChoice) { if (lambda == null) { @@ -2100,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 @@ -2186,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) { @@ -2195,11 +2231,11 @@ internal LoopGotoTargets(string label, LabelTarget breakLabel, LabelTarget conti this.ContinueLabel = continueLabel; } - internal string Label { get; private set; } + internal string Label { get; } - internal LabelTarget ContinueLabel { get; private set; } + internal LabelTarget ContinueLabel { get; } - internal LabelTarget BreakLabel { get; private set; } + internal LabelTarget BreakLabel { get; } } private LabelTarget _returnTarget; @@ -2207,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; @@ -2254,11 +2291,9 @@ private Expression CaptureAstResults( exprs.Add(Expression.Assign(resultList, Expression.New(CachedReflectionInfo.ObjectList_ctor))); exprs.Add(Expression.Assign(s_getCurrentPipe, Expression.New(CachedReflectionInfo.Pipe_ctor, resultList))); exprs.Add(Expression.Call(oldPipe, CachedReflectionInfo.Pipe_SetVariableListForTemporaryPipe, s_getCurrentPipe)); - if (generateRedirectExprs != null) - { - // Add merge redirection expressions if delegate is provided. - generateRedirectExprs(exprs, finallyExprs); - } + + // Add merge redirection expressions if delegate is provided. + generateRedirectExprs?.Invoke(exprs, finallyExprs); exprs.Add(Compile(ast)); @@ -2272,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; @@ -2374,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( @@ -2414,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; @@ -2447,10 +2482,17 @@ public object VisitScriptBlock(ScriptBlockAst scriptBlockAst) { if (!scriptBlockAst.EndBlock.Unnamed) { - funcName = funcName + ""; + funcName += ""; } _endBlockLambda = CompileNamedBlock(scriptBlockAst.EndBlock, funcName, rootForDefiningTypesAndUsings); + rootForDefiningTypesAndUsings = null; + } + + if (scriptBlockAst.CleanBlock != null) + { + _cleanBlockLambda = CompileNamedBlock(scriptBlockAst.CleanBlock, funcName + "", rootForDefiningTypesAndUsings); + rootForDefiningTypesAndUsings = null; } return null; @@ -2554,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 }, @@ -2580,7 +2622,7 @@ private Expression> CompileSingleLambda( return Expression.Lambda>(body, funcName, new[] { s_functionContext }); } - private void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List exprs) + private static void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List exprs) { // We don't postpone load assemblies, import modules from 'using' to the moment, when enclosed scriptblock is executed. // We do loading, when root of the script is compiled. @@ -2593,12 +2635,12 @@ private void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings { 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(); @@ -2616,9 +2658,9 @@ private void GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings } 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( @@ -2782,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); @@ -3267,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; @@ -3279,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. @@ -3572,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 @@ -3683,7 +3735,7 @@ public object VisitPipeline(PipelineAst pipelineAst) var temps = new List(); var exprs = new List(); - if (!(pipelineAst.Parent is AssignmentStatementAst || pipelineAst.Parent is ParenExpressionAst)) + if (pipelineAst.Parent is not AssignmentStatementAst && pipelineAst.Parent is not ParenExpressionAst) { // If the parent is an assignment, we've already added a sequence point, don't add another. exprs.Add(UpdatePosition(pipelineAst)); @@ -3766,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 { @@ -3827,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 @@ -3859,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.) // @@ -3870,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; @@ -3898,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"); @@ -4241,7 +4290,8 @@ public object VisitCommandParameter(CommandParameterAst commandParameterAst) Expression.Constant(errorPos.Text), Expression.Constant(arg), Expression.Convert(GetCommandArgumentExpression(arg), typeof(object)), - ExpressionCache.Constant(spaceAfterParameter)); + ExpressionCache.Constant(spaceAfterParameter), + ExpressionCache.Constant(false)); } return Expression.Call( @@ -4264,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[] { @@ -5129,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; @@ -5141,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; @@ -5490,7 +5540,7 @@ where t.Label.Equals(labelStrAst.Value, StringComparison.OrdinalIgnoreCase) } else { - labelExpr = labelExpr ?? ExpressionCache.ConstEmptyString; + labelExpr ??= ExpressionCache.ConstEmptyString; result = Expression.Throw(Expression.New(nonLocalExceptionCtor, labelExpr.Convert(typeof(string)))); } @@ -5568,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) @@ -5652,7 +5704,7 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) if (rhs is ConstantExpression && rhs.Type == typeof(Type)) { var isType = (Type)((ConstantExpression)rhs).Value; - if (!(isType == typeof(PSCustomObject)) && !(isType == typeof(PSObject))) + if (isType != typeof(PSCustomObject) && isType != typeof(PSObject)) { lhs = lhs.Type.IsValueType ? lhs : Expression.Call(CachedReflectionInfo.PSObject_Base, lhs); if (binaryExpressionAst.Operator == TokenKind.Is) @@ -5923,14 +5975,12 @@ public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) private static Expression GetLikeRHSOperand(WildcardOptions options, Expression expr) { - var constExpr = expr as ConstantExpression; - if (constExpr == null) + if (!(expr is ConstantExpression constExpr)) { return expr; } - var val = constExpr.Value as string; - if (val == null) + if (!(constExpr.Value is string val)) { return expr; } @@ -6086,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), @@ -6099,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)) { @@ -6299,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 != null ? arguments.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray() : null); + + 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) { @@ -6320,9 +6396,7 @@ internal static PSMethodInvocationConstraints GetInvokeMemberConstraints(BaseCto Diagnostics.Assert(false, "BaseCtorInvokeMemberExpressionAst must be used only inside TypeDefinitionAst"); } - return CombineTypeConstraintForMethodResolution( - targetTypeConstraint, - arguments != null ? arguments.Select(Compiler.GetTypeConstraintForMethodResolution).ToArray() : null); + return CombineTypeConstraintForMethodResolution(targetTypeConstraint, argumentTypes, genericArguments: null); } internal Expression InvokeMember( @@ -6335,7 +6409,7 @@ internal Expression InvokeMember( bool nullConditional = false) { var callInfo = new CallInfo(args.Count()); - var classScope = _memberFunctionType != null ? _memberFunctionType.Type : null; + var classScope = _memberFunctionType?.Type; var binder = name.Equals("new", StringComparison.OrdinalIgnoreCase) && @static ? (CallSiteBinder)PSCreateInstanceBinder.Get(callInfo, constraints, publicTypeOnly: true) : PSInvokeMemberBinder.Get(name, callInfo, @static, propertySet, constraints, classScope); @@ -6345,7 +6419,7 @@ internal Expression InvokeMember( return nullConditional ? GetNullConditionalWrappedExpression(target, dynamicExprFromBinder) : dynamicExprFromBinder; } - private Expression InvokeBaseCtorMethod(PSMethodInvocationConstraints constraints, Expression target, IEnumerable args) + private static Expression InvokeBaseCtorMethod(PSMethodInvocationConstraints constraints, Expression target, IEnumerable args) { var callInfo = new CallInfo(args.Count()); var binder = PSInvokeBaseCtorBinder.Get(callInfo, constraints); @@ -6418,7 +6492,7 @@ public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) } } - values = values ?? CaptureAstResults(subExpr, CaptureAstContext.Enumerable); + values ??= CaptureAstResults(subExpr, CaptureAstContext.Enumerable); if (pureExprAst is ArrayLiteralAst) { @@ -6668,9 +6742,20 @@ private IEnumerable GetArgumentExprs(Compiler compiler) return _argExprTemps; } - return InvokeMemberExpressionAst.Arguments == null - ? Array.Empty() - : InvokeMemberExpressionAst.Arguments.Select(compiler.Compile).ToArray(); + ReadOnlyCollection arguments = InvokeMemberExpressionAst.Arguments; + + if (arguments is null || arguments.Count == 0) + { + return Array.Empty(); + } + + var result = new Expression[arguments.Count]; + for (int i = 0; i < result.Length; i++) + { + result[i] = compiler.Compile(arguments[i]); + } + + return result; } public Expression GetValue(Compiler compiler, List exprs, List temps) @@ -6683,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; @@ -6896,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 5813ed9d41f..d580148ddd2 100644 --- a/src/System.Management.Automation/engine/parser/ConstantValues.cs +++ b/src/System.Management.Automation/engine/parser/ConstantValues.cs @@ -53,7 +53,9 @@ public static bool IsConstant(Ast ast, out object constantValue, bool forAttribu } internal bool CheckingAttributeArgument { get; set; } + internal bool CheckingClassAttributeArguments { get; set; } + internal bool CheckingRequiresArgument { get; set; } public object VisitErrorStatement(ErrorStatementAst errorStatementAst) { return false; } @@ -146,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); } @@ -160,8 +170,7 @@ public object VisitPipeline(PipelineAst pipelineAst) private static bool IsNullDivisor(ExpressionAst operand) { - var varExpr = operand as VariableExpressionAst; - if (varExpr == null) + if (!(operand is VariableExpressionAst varExpr)) { return false; } @@ -261,7 +270,7 @@ public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) { - if (!memberExpressionAst.Static || !(memberExpressionAst.Expression is TypeExpressionAst)) + if (!memberExpressionAst.Static || memberExpressionAst.Expression is not TypeExpressionAst) { return false; } @@ -272,8 +281,7 @@ public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) return false; } - var member = memberExpressionAst.Member as StringConstantExpressionAst; - if (member == null) + if (!(memberExpressionAst.Member is StringConstantExpressionAst member)) { return false; } @@ -324,6 +332,7 @@ public object VisitParenExpression(ParenExpressionAst parenExpressionAst) internal class ConstantValueVisitor : ICustomAstVisitor2 { internal bool AttributeArgument { get; set; } + internal bool RequiresArgument { get; set; } [Conditional("DEBUG")] diff --git a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs index bc65cefa866..cb280bdaf61 100644 --- a/src/System.Management.Automation/engine/parser/DebugViewWriter.cs +++ b/src/System.Management.Automation/engine/parser/DebugViewWriter.cs @@ -470,7 +470,7 @@ protected override Expression VisitLambda(Expression node) { private static bool IsSimpleExpression(Expression node) { var binary = node as BinaryExpression; if (binary != null) { - return !(binary.Left is BinaryExpression || binary.Right is BinaryExpression); + return binary.Left is not BinaryExpression && binary.Right is not BinaryExpression; } return false; @@ -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 93e11f598b1..00000000000 --- a/src/System.Management.Automation/engine/parser/FusionAssemblyIdentity.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -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 467f3f2c5e7..00000000000 --- a/src/System.Management.Automation/engine/parser/GlobalAssemblyCache.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -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 8a18f880bab..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; @@ -14,7 +15,7 @@ namespace System.Management.Automation.Language { - internal class TypeDefiner + internal static class TypeDefiner { internal const string DynamicClassAssemblyName = "PowerShell Class Assembly"; internal const string DynamicClassAssemblyFullNamePrefix = "PowerShell Class Assembly,"; @@ -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); @@ -921,7 +953,7 @@ private void DefineConstructor(IParameterMetadataProvider ipmp, ReadOnlyCollecti (i, n) => ctor.DefineParameter(i, ParameterAttributes.None, n)); } - private string GetMetaDataName(string name, int numberOfParameters) + private static string GetMetaDataName(string name, int numberOfParameters) { int currentId = Interlocked.Increment(ref s_globalCounter); string metaDataName = name + "_" + numberOfParameters + "_" + currentId; @@ -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)); @@ -1411,7 +1442,7 @@ private static string GetClassNameInAssembly(TypeDefinitionAst typeDefinitionAst { if (parent is IParameterMetadataProvider) { - nameParts = nameParts ?? new List(); + nameParts ??= new List(); var fnDefn = parent.Parent as FunctionDefinitionAst; if (fnDefn != null) { @@ -1434,10 +1465,10 @@ private static string GetClassNameInAssembly(TypeDefinitionAst typeDefinitionAst nameParts.Reverse(); nameParts.Add(typeDefinitionAst.Name); - return string.Join(".", nameParts); + return string.Join('.', nameParts); } - private static OpCode[] s_ldc = + private static readonly OpCode[] s_ldc = { OpCodes.Ldc_I4_0, OpCodes.Ldc_I4_1, OpCodes.Ldc_I4_2, OpCodes.Ldc_I4_3, OpCodes.Ldc_I4_4, OpCodes.Ldc_I4_5, OpCodes.Ldc_I4_6, OpCodes.Ldc_I4_7, OpCodes.Ldc_I4_8 @@ -1455,7 +1486,7 @@ private static void EmitLdc(ILGenerator emitter, int c) } } - private static OpCode[] s_ldarg = + private static readonly OpCode[] s_ldarg = { OpCodes.Ldarg_0, OpCodes.Ldarg_1, OpCodes.Ldarg_2, OpCodes.Ldarg_3 }; @@ -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 25622b80eda..1592d2e7e7d 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -9,9 +9,13 @@ 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; +using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal; namespace System.Management.Automation.Language { @@ -139,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; @@ -198,7 +204,7 @@ private ScriptBlockAst ParseTask(string fileName, string input, List toke } catch (InsufficientExecutionStackException) { - if (recursed == false) + if (!recursed) { // We'll try parsing once more, this time on a new thread. The assumption here is // that the stack was close to overflowing before we tried to parse, and that won't @@ -272,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; } @@ -436,8 +441,7 @@ private Token NextToken() private Token PeekToken() { Token token = _ungotToken ?? _tokenizer.NextToken(); - if (_ungotToken == null) - _ungotToken = token; + _ungotToken ??= token; return token; } @@ -716,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; } @@ -732,8 +737,7 @@ internal static bool TryParseAsConstantHashtable(string input, out Hashtable res return false; } - var pipelineAst = statements[0] as PipelineAst; - if (pipelineAst == null) + if (!(statements[0] is PipelineAst pipelineAst)) { return false; } @@ -744,8 +748,7 @@ internal static bool TryParseAsConstantHashtable(string input, out Hashtable res return false; } - var hashTableAst = expr as HashtableAst; - if (hashTableAst == null) + if (!(expr is HashtableAst hashTableAst)) { return false; } @@ -817,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. @@ -1347,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. @@ -1431,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(); @@ -1440,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(); @@ -1461,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); } @@ -1510,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) { @@ -1528,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 ']'. @@ -1564,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(); @@ -1668,7 +1699,7 @@ private ScriptBlockAst ScriptBlockBodyRule(Token lCurly, List statements.Add(predefinedStatementAst); } - IScriptExtent statementListExtent = paramBlockAst != null ? paramBlockAst.Extent : null; + IScriptExtent statementListExtent = paramBlockAst?.Extent; IScriptExtent scriptBlockExtent; while (true) @@ -1707,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 != null) ? paramBlockAst.Extent : null; + NamedBlockAst cleanBlock = null; + + IScriptExtent startExtent = lCurly?.Extent ?? paramBlockAst?.Extent; IScriptExtent endExtent = null; IScriptExtent extent = null; IScriptExtent scriptBlockExtent = null; @@ -1751,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; @@ -1791,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); @@ -1812,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); } @@ -1887,10 +1927,7 @@ private IScriptExtent StatementListRule(List statements, List !(attr is AttributeAst))) + foreach (var attr in attributes.Where(static attr => attr is not AttributeAst)) { ReportError(attr.Extent, nameof(ParserStrings.TypeNotAllowedBeforeStatement), @@ -2044,7 +2081,7 @@ private StatementAst StatementRule() statement = BlockStatementRule(token); break; case TokenKind.Configuration: - statement = ConfigurationStatementRule(attributes != null ? attributes.OfType() : null, token); + statement = ConfigurationStatementRule(attributes?.OfType(), token); break; case TokenKind.From: case TokenKind.Define: @@ -2423,7 +2460,7 @@ private StatementAst IfStatementRule(Token ifToken) UngetToken(rParen); // Don't bother reporting this error if we already reported an empty condition error. - if (!(condition is ErrorStatementAst)) + if (condition is not ErrorStatementAst) { ReportIncompleteInput(rParen.Extent, nameof(ParserStrings.MissingEndParenthesisAfterStatement), @@ -2550,7 +2587,7 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke { SkipToken(); endErrorStatement = switchParameterToken.Extent; - specifiedFlags = specifiedFlags ?? new Dictionary>(); + specifiedFlags ??= new Dictionary>(); if (IsSpecificParameter(switchParameterToken, "regex")) { @@ -2647,7 +2684,7 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke if (switchParameterToken.Kind == TokenKind.Minus) { - specifiedFlags = specifiedFlags ?? new Dictionary>(); + specifiedFlags ??= new Dictionary>(); specifiedFlags.Add(VERBATIM_ARGUMENT, new Tuple(switchParameterToken, null)); } @@ -2850,7 +2887,7 @@ private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToke } return new SwitchStatementAst(ExtentOf(labelToken ?? switchToken, rCurly), - labelToken != null ? labelToken.LabelText : null, condition, flags, clauses, @default); + labelToken?.LabelText, condition, flags, clauses, @default); } private StatementAst ConfigurationStatementRule(IEnumerable customAttributes, Token configurationToken) @@ -2885,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); @@ -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; } @@ -2969,7 +3027,6 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom ExpressionAst configurationBodyScriptBlock = null; - // Automatically import the PSDesiredStateConfiguration module at this point. PowerShell p = null; // Save the parser we're using so we can resume the current parse when we're done. @@ -2997,7 +3054,18 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom { // Load the default CIM keywords Collection CIMKeywordErrors = new Collection(); - Microsoft.PowerShell.DesiredStateConfiguration.Internal.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 + { + Dsc.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors); + } // Report any errors encountered while loading CIM dynamic keywords. if (CIMKeywordErrors.Count > 0) @@ -3033,10 +3101,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom } finally { - if (p != null) - { - p.Dispose(); - } + p?.Dispose(); // // Put the parser back... @@ -3174,10 +3239,7 @@ private StatementAst ConfigurationStatementRule(IEnumerable custom if (topLevel) { - if (_configurationKeywordsDefinedInThisFile == null) - { - _configurationKeywordsDefinedInThisFile = new Dictionary(); - } + _configurationKeywordsDefinedInThisFile ??= new Dictionary(); _configurationKeywordsDefinedInThisFile[keywordToAddForThisConfigurationStatement.Keyword] = keywordToAddForThisConfigurationStatement; } @@ -3239,7 +3301,16 @@ 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. // - Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.ClearCache(); + ICrossPlatformDsc dscSubsystem = SubsystemManager.GetSubsystem(); + if (dscSubsystem != null) + { + dscSubsystem.ClearCache(); + } + else + { + Dsc.DscClassCache.ClearCache(); + } + System.Management.Automation.Language.DynamicKeyword.Reset(); } @@ -3441,7 +3512,7 @@ private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachTo } return new ForEachStatementAst(ExtentOf(startOfStatement, body), - labelToken != null ? labelToken.LabelText : null, + labelToken?.LabelText, flags, throttleLimit, variableAst, pipeline, body); } @@ -3523,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), @@ -3554,7 +3622,7 @@ private StatementAst ForStatementRule(LabelToken labelToken, Token forToken) } return new ForStatementAst(ExtentOf(labelToken ?? forToken, body), - labelToken != null ? labelToken.LabelText : null, initializer, condition, iterator, body); + labelToken?.LabelText, initializer, condition, iterator, body); } private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) @@ -3611,7 +3679,7 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) // so stop parsing the statement and try parsing something else if possible. UngetToken(rParen); - if (!(condition is ErrorStatementAst)) + if (condition is not ErrorStatementAst) { ReportIncompleteInput(After(condition), nameof(ParserStrings.MissingEndParenthesisAfterStatement), @@ -3636,7 +3704,7 @@ private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken) } return new WhileStatementAst(ExtentOf(labelToken ?? whileToken, body), - labelToken != null ? labelToken.LabelText : null, condition, body); + labelToken?.LabelText, condition, body); } /// @@ -3835,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(); @@ -4131,7 +4196,7 @@ private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken) } IScriptExtent extent = ExtentOf(startExtent, rParen); - string label = (labelToken != null) ? labelToken.LabelText : null; + string label = labelToken?.LabelText; if (whileOrUntilToken.Kind == TokenKind.Until) { return new DoUntilStatementAst(extent, label, condition, body); @@ -4160,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(); @@ -4198,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)), @@ -4256,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; @@ -4285,13 +4356,10 @@ private StatementAst ClassDefinitionRule(List customAttributes ? customAttributes[0].Extent : classToken.Extent; var extent = ExtentOf(startExtent, lastExtent); - var classDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType(), members, TypeAttributes.Class, superClassesList); + 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); @@ -4355,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) @@ -4378,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) { @@ -4576,7 +4638,7 @@ private MemberAst ClassMemberRule(string className, out List astsOnError) return null; } - private bool TryUseTokenAsSimpleName(Token token) + private static bool TryUseTokenAsSimpleName(Token token) { if (token.Kind == TokenKind.Identifier || token.Kind == TokenKind.DynamicKeyword @@ -4589,32 +4651,26 @@ private bool TryUseTokenAsSimpleName(Token token) return false; } - private void RecordErrorAsts(Ast errAst, ref List astsOnError) + private static void RecordErrorAsts(Ast errAst, ref List astsOnError) { if (errAst == null) { return; } - if (astsOnError == null) - { - astsOnError = new List(); - } + astsOnError ??= new List(); astsOnError.Add(errAst); } - private void RecordErrorAsts(IEnumerable errAsts, ref List astsOnError) + private static void RecordErrorAsts(IEnumerable errAsts, ref List astsOnError) { if (errAsts == null || !errAsts.Any()) { return; } - if (astsOnError == null) - { - astsOnError = new List(); - } + astsOnError ??= new List(); astsOnError.AddRange(errAsts); } @@ -4644,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; @@ -4683,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( @@ -4746,7 +4801,7 @@ private StatementAst EnumDefinitionRule(List customAttributes, ? customAttributes[0].Extent : enumToken.Extent; var extent = ExtentOf(startExtent, rCurly); - var enumDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType(), members, TypeAttributes.Enum, underlyingTypeConstraint == null ? null : new[] { underlyingTypeConstraint }); + var enumDefn = new TypeDefinitionAst(extent, name.Value, customAttributes?.OfType(), members, TypeAttributes.Enum, underlyingTypeConstraint == null ? null : new[] { underlyingTypeConstraint }); if (customAttributes != null && customAttributes.OfType().Any()) { // No need to report error since there is error reported in method StatementRule @@ -4923,7 +4978,8 @@ private StatementAst UsingStatementRule(Token usingToken) return new ErrorStatementAst(ExtentOf(usingToken, itemToken.Extent)); } - if (!(itemAst is StringConstantExpressionAst) && (kind != UsingStatementKind.Module || !(itemAst is HashtableAst))) + if (itemAst is not StringConstantExpressionAst + && (kind != UsingStatementKind.Module || itemAst is not HashtableAst)) { ReportError(ExtentFromFirstOf(itemAst, itemToken), nameof(ParserStrings.InvalidValueForUsingItemName), @@ -4952,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), @@ -4961,14 +5017,32 @@ 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) { htAst = (HashtableAst)aliasAst; } - else if (!(aliasAst is StringConstantExpressionAst)) + 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(); @@ -5070,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 { @@ -5158,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(); @@ -5185,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(); @@ -5444,7 +5509,7 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L List exceptionTypes = null; Token commaToken = null; - do + while (true) { var restorePoint = _tokenizer.GetRestorePoint(); SkipNewlines(); @@ -5478,10 +5543,7 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L break; } - if (exceptionTypes == null) - { - exceptionTypes = new List(); - } + exceptionTypes ??= new List(); exceptionTypes.Add(typeConstraintAst); @@ -5493,7 +5555,7 @@ private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref L } SkipToken(); - } while (true); + } StatementBlockAst handler = StatementBlockRule(); if (handler == null) @@ -5624,7 +5686,7 @@ private StatementAst DataStatementRule(Token dataToken) IScriptExtent endErrorStatement = null; SkipNewlines(); var dataVariableNameAst = SimpleNameRule(); - string dataVariableName = (dataVariableNameAst != null) ? dataVariableNameAst.Value : null; + string dataVariableName = dataVariableNameAst?.Value; SkipNewlines(); Token supportedCommandToken = PeekToken(); @@ -5722,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; @@ -5984,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); @@ -5999,7 +6058,7 @@ private PipelineBaseAst PipelineRule( commandAst = new CommandExpressionAst( exprExtent, expr, - redirections?.Where(r => r != null)); + redirections?.Where(static r => r != null)); } else { @@ -6008,10 +6067,7 @@ private PipelineBaseAst PipelineRule( if (commandAst != null) { - if (startExtent == null) - { - startExtent = commandAst.Extent; - } + startExtent ??= commandAst.Extent; pipelineElements.Add(commandAst); } @@ -6364,10 +6420,7 @@ private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token t } commaToken = token; - if (commandArgs == null) - { - commandArgs = new List(); - } + commandArgs ??= new List(); commandArgs.Add(exprAst); @@ -6535,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); } @@ -6630,7 +6680,7 @@ internal Ast CommandRule(bool forDynamicKeyword) return new CommandAst(ExtentOf(firstToken, endExtent), elements, dotSource || ampersand ? firstToken.Kind : TokenKind.Unknown, - redirections != null ? redirections.Where(r => r != null) : null); + redirections?.Where(static r => r != null)); } #endregion Pipelines @@ -6677,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) { @@ -6703,7 +6753,7 @@ private ExpressionAst ExpressionRule(bool endNumberOnTernaryOpChars = false) UngetToken(token); // Don't bother reporting this error if we already reported an empty 'IfTrue' operand error. - if (!(ifTrue is ErrorExpressionAst)) + if (ifTrue is not ErrorExpressionAst) { componentAsts.Add(ifTrue); ReportIncompleteInput( @@ -7104,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); @@ -7140,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) @@ -7689,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 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) + 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; @@ -7727,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); @@ -7740,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) @@ -7830,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 @@ -7852,7 +8006,7 @@ private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token l UngetToken(rBracket); // Skip reporting the error if we've already reported a missing index. - if (!(indexExpr is ErrorExpressionAst)) + if (indexExpr is not ErrorExpressionAst) { ReportIncompleteInput(After(indexExpr), nameof(ParserStrings.MissingEndSquareBracket), @@ -7935,12 +8089,12 @@ 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 { - get { return t_arrayOfOneArg ?? (t_arrayOfOneArg = new object[1]); } + get { return t_arrayOfOneArg ??= new object[1]; } } [ThreadStatic] @@ -7948,7 +8102,7 @@ private static object[] arrayOfOneArg private static object[] arrayOfTwoArgs { - get { return t_arrayOfTwoArgs ?? (t_arrayOfTwoArgs = new object[2]); } + get { return t_arrayOfTwoArgs ??= new object[2]; } } [ThreadStatic] @@ -8014,7 +8168,7 @@ internal void ReportError(ParseError error) SaveError(error); } - private void ReportErrorsAsWarnings(Collection errors) + private static void ReportErrorsAsWarnings(Collection errors) { var executionContext = Runspaces.Runspace.DefaultRunspace.ExecutionContext; if (executionContext != null && executionContext.InternalHost != null && executionContext.InternalHost.UI != null) diff --git a/src/System.Management.Automation/engine/parser/Position.cs b/src/System.Management.Automation/engine/parser/Position.cs index e9f273fed14..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,18 +46,20 @@ 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. /// +#nullable enable public interface IScriptExtent { /// /// The filename the extent includes, or null if the extent is not included in any file. /// - string File { get; } + string? File { get; } /// /// The starting position of the extent. @@ -103,6 +106,7 @@ public interface IScriptExtent /// int EndOffset { get; } } +#nullable restore /// /// A few utility functions for script positions. @@ -334,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) @@ -352,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); } @@ -571,8 +580,7 @@ internal sealed class EmptyScriptExtent : IScriptExtent public override bool Equals(object obj) { - IScriptExtent otherPosition = obj as IScriptExtent; - if (otherPosition == null) + if (!(obj is IScriptExtent otherPosition)) { return false; } @@ -645,8 +653,8 @@ public ScriptPosition( int scriptLineNumber, int offsetInLine, string line, - string fullScript) : - this(scriptName, scriptLineNumber, offsetInLine, line) + string fullScript) + : this(scriptName, scriptLineNumber, offsetInLine, line) { _fullScript = fullScript; } @@ -763,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 b865a76ee40..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,128 +37,181 @@ 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; } /// - public virtual AstVisitAction VisitErrorStatement(ErrorStatementAst errorStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction DefaultVisit(Ast ast) => AstVisitAction.Continue; + /// - public virtual AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitErrorStatement(ErrorStatementAst errorStatementAst) => DefaultVisit(errorStatementAst); + /// - public virtual AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => DefaultVisit(errorExpressionAst); + + /// + public virtual AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) => DefaultVisit(scriptBlockAst); + /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "param")] - public virtual AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitParamBlock(ParamBlockAst paramBlockAst) => DefaultVisit(paramBlockAst); + /// - public virtual AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitNamedBlock(NamedBlockAst namedBlockAst) => DefaultVisit(namedBlockAst); + /// - public virtual AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => DefaultVisit(typeConstraintAst); + /// - public virtual AstVisitAction VisitAttribute(AttributeAst attributeAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitAttribute(AttributeAst attributeAst) => DefaultVisit(attributeAst); + /// - public virtual AstVisitAction VisitParameter(ParameterAst parameterAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitParameter(ParameterAst parameterAst) => DefaultVisit(parameterAst); + /// - public virtual AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTypeExpression(TypeExpressionAst typeExpressionAst) => DefaultVisit(typeExpressionAst); + /// - public virtual AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => DefaultVisit(functionDefinitionAst); + /// - public virtual AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitStatementBlock(StatementBlockAst statementBlockAst) => DefaultVisit(statementBlockAst); + /// - public virtual AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitIfStatement(IfStatementAst ifStmtAst) => DefaultVisit(ifStmtAst); + /// - public virtual AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTrap(TrapStatementAst trapStatementAst) => DefaultVisit(trapStatementAst); + /// - public virtual AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitSwitchStatement(SwitchStatementAst switchStatementAst) => DefaultVisit(switchStatementAst); + /// - public virtual AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) => DefaultVisit(dataStatementAst); + /// - public virtual AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) => DefaultVisit(forEachStatementAst); + /// - public virtual AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => DefaultVisit(doWhileStatementAst); + /// - public virtual AstVisitAction VisitForStatement(ForStatementAst forStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitForStatement(ForStatementAst forStatementAst) => DefaultVisit(forStatementAst); + /// - public virtual AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitWhileStatement(WhileStatementAst whileStatementAst) => DefaultVisit(whileStatementAst); + /// - public virtual AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitCatchClause(CatchClauseAst catchClauseAst) => DefaultVisit(catchClauseAst); + /// - public virtual AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst) => DefaultVisit(tryStatementAst); + /// - public virtual AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) => DefaultVisit(breakStatementAst); + /// - public virtual AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitContinueStatement(ContinueStatementAst continueStatementAst) => DefaultVisit(continueStatementAst); + /// - public virtual AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst) => DefaultVisit(returnStatementAst); + /// - public virtual AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitExitStatement(ExitStatementAst exitStatementAst) => DefaultVisit(exitStatementAst); + /// - public virtual AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitThrowStatement(ThrowStatementAst throwStatementAst) => DefaultVisit(throwStatementAst); + /// - public virtual AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => DefaultVisit(doUntilStatementAst); + /// - public virtual AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) => DefaultVisit(assignmentStatementAst); + /// - public virtual AstVisitAction VisitPipeline(PipelineAst pipelineAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitPipeline(PipelineAst pipelineAst) => DefaultVisit(pipelineAst); + /// - public virtual AstVisitAction VisitCommand(CommandAst commandAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitCommand(CommandAst commandAst) => DefaultVisit(commandAst); + /// - public virtual AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitCommandExpression(CommandExpressionAst commandExpressionAst) => DefaultVisit(commandExpressionAst); + /// - public virtual AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitCommandParameter(CommandParameterAst commandParameterAst) => DefaultVisit(commandParameterAst); + /// - public virtual AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitMergingRedirection(MergingRedirectionAst redirectionAst) => DefaultVisit(redirectionAst); + /// - public virtual AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitFileRedirection(FileRedirectionAst redirectionAst) => DefaultVisit(redirectionAst); + /// - public virtual AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => DefaultVisit(binaryExpressionAst); + /// - public virtual AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => DefaultVisit(unaryExpressionAst); + /// - public virtual AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => DefaultVisit(convertExpressionAst); + /// - public virtual AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => DefaultVisit(constantExpressionAst); + /// - public virtual AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) => DefaultVisit(stringConstantExpressionAst); + /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubExpression")] [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "subExpression")] - public virtual AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitSubExpression(SubExpressionAst subExpressionAst) => DefaultVisit(subExpressionAst); + /// - public virtual AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitUsingExpression(UsingExpressionAst usingExpressionAst) => DefaultVisit(usingExpressionAst); + /// - public virtual AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) => DefaultVisit(variableExpressionAst); + /// - public virtual AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitMemberExpression(MemberExpressionAst memberExpressionAst) => DefaultVisit(memberExpressionAst); + /// - public virtual AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst) => DefaultVisit(methodCallAst); + /// - public virtual AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => DefaultVisit(arrayExpressionAst); + /// - public virtual AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => DefaultVisit(arrayLiteralAst); + /// - public virtual AstVisitAction VisitHashtable(HashtableAst hashtableAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitHashtable(HashtableAst hashtableAst) => DefaultVisit(hashtableAst); + /// - public virtual AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) => DefaultVisit(scriptBlockExpressionAst); + /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Paren")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "paren")] - public virtual AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitParenExpression(ParenExpressionAst parenExpressionAst) => DefaultVisit(parenExpressionAst); + /// - public virtual AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => DefaultVisit(expandableStringExpressionAst); + /// - public virtual AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitIndexExpression(IndexExpressionAst indexExpressionAst) => DefaultVisit(indexExpressionAst); + /// - public virtual AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => DefaultVisit(attributedExpressionAst); + /// - public virtual AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitBlockStatement(BlockStatementAst blockStatementAst) => DefaultVisit(blockStatementAst); + /// - public virtual AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => DefaultVisit(namedAttributeArgumentAst); } /// @@ -167,37 +220,38 @@ internal AstVisitAction CheckForPostAction(Ast ast, AstVisitAction action) public abstract class AstVisitor2 : AstVisitor { /// - public virtual AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst) => DefaultVisit(typeDefinitionAst); /// - public virtual AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitPropertyMember(PropertyMemberAst propertyMemberAst) => DefaultVisit(propertyMemberAst); /// - public virtual AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitFunctionMember(FunctionMemberAst functionMemberAst) => DefaultVisit(functionMemberAst); /// - public virtual AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst) => DefaultVisit(baseCtorInvokeMemberExpressionAst); /// - public virtual AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) => DefaultVisit(usingStatementAst); /// - public virtual AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) => DefaultVisit(configurationDefinitionAst); /// - public virtual AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordStatementAst) => DefaultVisit(dynamicKeywordStatementAst); /// - public virtual AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitTernaryExpression(TernaryExpressionAst ternaryExpressionAst) => DefaultVisit(ternaryExpressionAst); /// - public virtual AstVisitAction VisitPipelineChain(PipelineChainAst statementChain) { return AstVisitAction.Continue; } + public virtual AstVisitAction VisitPipelineChain(PipelineChainAst statementChain) => DefaultVisit(statementChain); } /// /// Implement this interface when you implement or when /// you want to do something after possibly visiting the children of the ast. /// +#nullable enable public interface IAstPostVisitHandler { /// diff --git a/src/System.Management.Automation/engine/parser/SafeValues.cs b/src/System.Management.Automation/engine/parser/SafeValues.cs index 37320de83d1..38181168a66 100644 --- a/src/System.Management.Automation/engine/parser/SafeValues.cs +++ b/src/System.Management.Automation/engine/parser/SafeValues.cs @@ -47,11 +47,15 @@ public static bool IsAstSafe(Ast ast, GetSafeValueVisitor.SafeValueContext safeV internal IsSafeValueVisitor(GetSafeValueVisitor.SafeValueContext safeValueContext) { _safeValueContext = safeValueContext; + + bool skipSizeCheck = safeValueContext is GetSafeValueVisitor.SafeValueContext.SkipHashtableSizeCheck; + _maxVisitCount = skipSizeCheck ? uint.MaxValue : 5000; + _maxHashtableKeyCount = skipSizeCheck ? int.MaxValue : 500; } internal bool IsAstSafe(Ast ast) { - if ((bool)ast.Accept(this) && _visitCount < MaxVisitCount) + if ((bool)ast.Accept(this) && _visitCount < _maxVisitCount) { return true; } @@ -65,8 +69,8 @@ internal bool IsAstSafe(Ast ast) // This is a check of the number of visits private uint _visitCount = 0; - private const uint MaxVisitCount = 5000; - private const int MaxHashtableKeyCount = 500; + private readonly uint _maxVisitCount; + private readonly int _maxHashtableKeyCount; // Used to determine if we are being called within a GetPowerShell() context, // which does some additional security verification outside of the scope of @@ -330,7 +334,7 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) public object VisitHashtable(HashtableAst hashtableAst) { - if (hashtableAst.KeyValuePairs.Count > MaxHashtableKeyCount) + if (hashtableAst.KeyValuePairs.Count > _maxHashtableKeyCount) { return false; } @@ -356,13 +360,14 @@ 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 { Default, GetPowerShell, - ModuleAnalysis + ModuleAnalysis, + SkipHashtableSizeCheck, } // future proofing @@ -371,6 +376,7 @@ private GetSafeValueVisitor() { } public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueContext safeValueContext) { t_context = context; + if (IsSafeValueVisitor.IsAstSafe(ast, safeValueContext)) { return ast.Accept(new GetSafeValueVisitor()); @@ -480,7 +486,7 @@ public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueCo // this can throw, but there really isn't useful information we can add, as the // offending expression will be presented in the case of any failure // - private object GetSingleValueFromTarget(object target, object index) + private static object GetSingleValueFromTarget(object target, object index) { var targetString = target as string; if (targetString != null) @@ -517,7 +523,7 @@ private object GetSingleValueFromTarget(object target, object index) throw new Exception(); } - private object GetIndexedValueFromTarget(object target, object index) + private static object GetIndexedValueFromTarget(object target, object index) { var indexArray = index as object[]; return indexArray != null ? ((object[])indexArray).Select(i => GetSingleValueFromTarget(target, i)).ToArray() : GetSingleValueFromTarget(target, index); @@ -528,7 +534,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)); } @@ -546,10 +553,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 f681a30d070..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; @@ -62,12 +65,12 @@ private bool AnalyzingStaticMember() return fnMemberAst != null ? fnMemberAst.IsStatic : ((PropertyMemberAst)currentMember).IsStatic; } - private bool IsValidAttributeArgument(Ast ast, IsConstantValueVisitor visitor) + private static bool IsValidAttributeArgument(Ast ast, IsConstantValueVisitor visitor) { return (bool)ast.Accept(visitor); } - private (string id, string msg) GetNonConstantAttributeArgErrorExpr(IsConstantValueVisitor visitor) + private static (string id, string msg) GetNonConstantAttributeArgErrorExpr(IsConstantValueVisitor visitor) { if (visitor.CheckingClassAttributeArguments) { @@ -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) { @@ -197,7 +196,8 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) var members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); - if (members.Length != 1 || !(members[0] is PropertyInfo || members[0] is FieldInfo)) + if (members.Length != 1 + || (members[0] is not PropertyInfo && members[0] is not FieldInfo)) { _parser.ReportError(namedArg.Extent, nameof(ParserStrings.PropertyNotFoundForAttribute), @@ -238,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), @@ -247,8 +247,6 @@ public override AstVisitAction VisitAttribute(AttributeAst attributeAst) } else { - names.Add(name); - if (!namedArg.ExpressionOmitted && !IsValidAttributeArgument(namedArg.Argument, constantValueVisitor)) { var error = GetNonConstantAttributeArgErrorExpr(constantValueVisitor); @@ -348,7 +346,7 @@ internal static void CheckArrayTypeNameDepth(ITypeName typeName, IScriptExtent e { int count = 0; ITypeName type = typeName; - while ((type is TypeName) == false) + while (type is not TypeName) { count++; if (count > 200) @@ -403,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), @@ -529,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) { @@ -546,7 +548,10 @@ public override AstVisitAction VisitTryStatement(TryStatementAst tryStatementAst break; } - if (block2.IsCatchAll) continue; + if (block2.IsCatchAll) + { + continue; + } foreach (TypeConstraintAst typeLiteral1 in block1.CatchTypes) { @@ -669,7 +674,7 @@ private static string GetLabel(ExpressionAst expr) } var str = expr as StringConstantExpressionAst; - return str != null ? str.Value : null; + return str?.Value; } public override AstVisitAction VisitBreakStatement(BreakStatementAst breakStatementAst) @@ -692,8 +697,7 @@ public override AstVisitAction VisitContinueStatement(ContinueStatementAst conti private void CheckForReturnStatement(ReturnStatementAst ast) { - var functionMemberAst = _memberScopeStack.Peek() as FunctionMemberAst; - if (functionMemberAst == null) + if (!(_memberScopeStack.Peek() is FunctionMemberAst functionMemberAst)) { return; } @@ -761,7 +765,7 @@ private void CheckAssignmentTarget(ExpressionAst ast, bool simpleAssignment, Act CheckAssignmentTarget(expr, simpleAssignment, reportError); } } - else if (!(ast is ISupportsAssignment)) + else if (ast is not ISupportsAssignment) { errorAst = ast; } @@ -907,7 +911,7 @@ public override AstVisitAction VisitConvertExpression(ConvertExpressionAst conve { if (convertExpressionAst.Type.TypeName.FullName.Equals(LanguagePrimitives.OrderedAttribute, StringComparison.OrdinalIgnoreCase)) { - if (!(convertExpressionAst.Child is HashtableAst)) + if (convertExpressionAst.Child is not HashtableAst) { // We allow the ordered attribute only on hashliteral node. // This check covers the following scenario @@ -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) @@ -1026,7 +1037,9 @@ private ExpressionAst CheckUsingExpression(ExpressionAst exprAst) } var memberExpr = exprAst as MemberExpressionAst; - if (memberExpr != null && !(memberExpr is InvokeMemberExpressionAst) && (memberExpr.Member is StringConstantExpressionAst)) + if (memberExpr != null + && memberExpr is not InvokeMemberExpressionAst + && memberExpr.Member is StringConstantExpressionAst) { return CheckUsingExpression(memberExpr.Expression); } @@ -1047,7 +1060,9 @@ private ExpressionAst CheckUsingExpression(ExpressionAst exprAst) public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst) { - if (variableExpressionAst.Splatted && !(variableExpressionAst.Parent is CommandAst) && !(variableExpressionAst.Parent is UsingExpressionAst)) + if (variableExpressionAst.Splatted + && variableExpressionAst.Parent is not CommandAst + && variableExpressionAst.Parent is not UsingExpressionAst) { if (variableExpressionAst.Parent is ArrayLiteralAst && variableExpressionAst.Parent.Parent is CommandAst) { @@ -1103,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; @@ -1120,10 +1135,6 @@ public override AstVisitAction VisitHashtable(HashtableAst hashtableAst) _parser.ReportError(entry.Item1.Extent, errorId, errorMsg, keyStr); } - else - { - keys.Add(keyStr); - } } } @@ -1179,10 +1190,10 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio return AstVisitAction.Continue; } - private void CheckMemberAccess(MemberExpressionAst ast) + private static void CheckMemberAccess(MemberExpressionAst ast) { // If the member access is not constant, it may be considered suspicious - if (!(ast.Member is ConstantExpressionAst)) + if (ast.Member is not ConstantExpressionAst) { MarkAstParentsAsSuspicious(ast); } @@ -1197,7 +1208,7 @@ private void CheckMemberAccess(MemberExpressionAst ast) } // Mark all of the parents of an AST as suspicious - private void MarkAstParentsAsSuspicious(Ast ast) + private static void MarkAstParentsAsSuspicious(Ast ast) { Ast targetAst = ast; var parent = ast; @@ -1214,7 +1225,9 @@ private void MarkAstParentsAsSuspicious(Ast ast) public override AstVisitAction VisitScriptBlock(ScriptBlockAst scriptBlockAst) { _scopeStack.Push(scriptBlockAst); - if (scriptBlockAst.Parent == null || scriptBlockAst.Parent is ScriptBlockExpressionAst || !(scriptBlockAst.Parent.Parent is FunctionMemberAst)) + if (scriptBlockAst.Parent == null + || scriptBlockAst.Parent is ScriptBlockExpressionAst + || scriptBlockAst.Parent.Parent is not FunctionMemberAst) { _memberScopeStack.Push(null); } @@ -1297,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) { // @@ -1380,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), @@ -1399,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()) { @@ -1446,7 +1492,9 @@ public void PostVisit(Ast ast) var scriptBlockAst = ast as ScriptBlockAst; if (scriptBlockAst != null) { - if (scriptBlockAst.Parent == null || scriptBlockAst.Parent is ScriptBlockExpressionAst || !(scriptBlockAst.Parent.Parent is FunctionMemberAst)) + if (scriptBlockAst.Parent == null + || scriptBlockAst.Parent is ScriptBlockExpressionAst + || scriptBlockAst.Parent.Parent is not FunctionMemberAst) { _memberScopeStack.Pop(); } @@ -1583,8 +1631,7 @@ private static void LookupRequiredMembers(Parser parser, TypeDefinitionAst typeD foreach (var baseType in typeDefinitionAst.BaseTypes) { - var baseTypeName = baseType.TypeName as TypeName; - if (baseTypeName == null) + if (!(baseType.TypeName is TypeName baseTypeName)) { continue; } @@ -1669,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 && @@ -1682,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()); @@ -1798,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); } } @@ -2271,7 +2336,7 @@ public override AstVisitAction VisitVariableExpression(VariableExpressionAst var else argBuilder.Append(", "); - argBuilder.Append("$"); + argBuilder.Append('$'); argBuilder.Append(varName); } diff --git a/src/System.Management.Automation/engine/parser/SymbolResolver.cs b/src/System.Management.Automation/engine/parser/SymbolResolver.cs index 42091ba2e7b..097bcabac3b 100644 --- a/src/System.Management.Automation/engine/parser/SymbolResolver.cs +++ b/src/System.Management.Automation/engine/parser/SymbolResolver.cs @@ -30,6 +30,7 @@ public TypeLookupResult(TypeDefinitionAst type = null) } public TypeDefinitionAst Type { get; set; } + public List ExternalNamespaces { get; set; } public bool IsAmbiguous() @@ -110,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 { @@ -178,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); @@ -274,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; @@ -301,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); @@ -353,7 +351,7 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - if (!(functionDefinitionAst.Parent is FunctionMemberAst)) + if (functionDefinitionAst.Parent is not FunctionMemberAst) { _symbolTable.EnterScope(functionDefinitionAst.Body, ScopeType.Function); } @@ -406,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, @@ -736,7 +734,7 @@ internal class SymbolResolvePostActionVisitor : DefaultCustomAstVisitor2 public override object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) { - if (!(functionDefinitionAst.Parent is FunctionMemberAst)) + if (functionDefinitionAst.Parent is not FunctionMemberAst) { _symbolResolver._symbolTable.LeaveScope(); } diff --git a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 77efa843f28..a7744ac6411 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -132,7 +132,7 @@ public TypeInferenceContext() : this(PowerShell.Create(RunspaceMode.CurrentRunsp } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// The powerShell instance passed need to have a non null Runspace. /// /// The instance of powershell to use for expression evaluation needed for type inference. @@ -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; } @@ -316,14 +336,24 @@ internal void AddMembersByInferredTypeDefinitionAst( // iterate through bases/interfaces foreach (var baseType in typename.TypeDefinitionAst.BaseTypes) { - var baseTypeName = baseType.TypeName as TypeName; - if (baseTypeName == null) + if (!(baseType.TypeName is TypeName baseTypeName)) { continue; } 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. @@ -354,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) @@ -417,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; } } @@ -537,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; @@ -555,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); + } } } @@ -639,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) @@ -656,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) @@ -687,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; @@ -750,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)); } @@ -821,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)); } @@ -930,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); } @@ -950,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) @@ -981,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; } @@ -1024,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); @@ -1178,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 _: @@ -1218,7 +1648,7 @@ bool IsInPropertyArgument(object o) } var previousPipelineElement = GetPreviousPipelineCommand(commandAst); - var typeName = "Microsoft.PowerShell.Commands.GroupInfo"; + const string typeName = "Microsoft.PowerShell.Commands.GroupInfo"; var members = new List(); foreach (var prevType in InferTypes(previousPipelineElement)) { @@ -1282,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]))); } } } @@ -1293,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) { @@ -1301,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; } @@ -1484,8 +1914,7 @@ private IEnumerable InferTypesFrom(MemberExpressionAst memberExpress var expression = memberExpressionAst.Expression; // If the member name isn't simple, don't even try. - var memberAsStringConst = memberCommandElement as StringConstantExpressionAst; - if (memberAsStringConst == null) + if (!(memberCommandElement is StringConstantExpressionAst memberAsStringConst)) { return Array.Empty(); } @@ -1496,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"); @@ -1507,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) @@ -1526,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, @@ -1535,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( @@ -1544,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++) @@ -1551,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; } @@ -1565,6 +2033,7 @@ private bool TryGetTypeFromMember( string memberName, ref bool maybeWantDefaultCtor, bool isInvokeMemberExpressionAst, + IList genericTypeArguments, List result, List memberNamesToCheck) { @@ -1590,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; } @@ -1639,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,35 +2170,76 @@ private bool TryGetTypeFromMember( return false; } - private void AddTypesFromMethodCacheEntry( + 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; - } - - // Accessing a method as a property, we'd return a wrapper over the method. - result.Add(new PSTypeName(typeof(PSMethod))); - } - - private PSTypeName[] GetExpressionType(ExpressionAst expression, bool isStatic) - { - PSTypeName[] exprType; - if (isStatic) - { - var exprAsType = expression as TypeExpressionAst; - if (exprAsType == null) + 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))); + } + + private PSTypeName[] GetExpressionType(ExpressionAst expression, bool isStatic) + { + PSTypeName[] exprType; + if (isStatic) + { + if (!(expression is TypeExpressionAst exprAsType)) { return null; } @@ -1772,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) { - // 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) + hasSeenScriptBlock = true; + } + else if (hasSeenScriptBlock) + { + 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() + { + ScopeIsLocal = true, + LocalScopeOnly = variableExpressionAst.VariablePath.IsLocal || variableExpressionAst.VariablePath.IsPrivate, + StopSearchOffset = variableExpressionAst.Extent.StartOffset, + VariableTarget = variableExpressionAst + }; + while (currentAst is not null) { - if (assignAst.Left is ConvertExpressionAst lhsConvert) + 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) { - smallestDiff = startOffset - endOffset; - closestAssignment = assignAst; + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(assignmentVisitor.LastAssignment))); + } + else + { + 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)) @@ -2024,7 +2536,7 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List /// The inferred types all the items in the array. /// The inferred strongly typed array type. - private PSTypeName GetArrayType(IEnumerable inferredTypes) + private static PSTypeName GetArrayType(IEnumerable inferredTypes) { PSTypeName foundType = null; foreach (PSTypeName inferredType in inferredTypes) @@ -2080,7 +2592,7 @@ private PSTypeName GetArrayType(IEnumerable inferredTypes) /// /// The type to infer enumerated item type from. /// The inferred enumerated item type. - private Type GetMostSpecificEnumeratedItemType(Type enumerableType) + private static Type GetMostSpecificEnumeratedItemType(Type enumerableType) { if (enumerableType.IsArray) { @@ -2144,19 +2656,19 @@ private Type GetMostSpecificEnumeratedItemType(Type enumerableType) /// The interface to test. /// /// A reference to a value indicating whether a non-generic enumerable type has been - /// seen. If is a non-generic enumerable type this - /// value will be set to . + /// seen. If is a non-generic enumerable type this + /// value will be set to . /// /// - /// A reference to a value indicating whether has been - /// seen. If is a this - /// value will be set to . + /// A reference to a value indicating whether has been + /// seen. If is a this + /// value will be set to . /// /// - /// The value of if it can be used to infer a specific - /// enumerated type, otherwise . + /// The value of if it can be used to infer a specific + /// enumerated type, otherwise . /// - private Type GetGenericCollectionLikeInterface( + private static Type GetGenericCollectionLikeInterface( Type interfaceType, ref bool hasSeenNonGeneric, ref bool hasSeenDictionaryEnumerator) @@ -2196,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) { @@ -2205,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()) { @@ -2253,14 +2786,14 @@ private IEnumerable InferTypeFrom(IndexExpressionAst indexExpression } /// - /// Infers the types as if they were enumerated. For example, a - /// of type would be returned as . + /// Infers the types as if they were enumerated. For example, a + /// of type would be returned as . /// /// /// The potentially enumerable types to infer enumerated type from. /// /// The enumerated item types. - private IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) + internal static IEnumerable GetInferredEnumeratedTypes(IEnumerable enumerableTypes) { foreach (PSTypeName maybeEnumerableType in enumerableTypes) { @@ -2281,8 +2814,7 @@ private IEnumerable GetInferredEnumeratedTypes(IEnumerable inferredTypes) { var argumentPair = argument as AstPair; - var scriptBlockExpressionAst = argumentPair?.Argument as ScriptBlockExpressionAst; - if (scriptBlockExpressionAst == null) + if (!(argumentPair?.Argument is ScriptBlockExpressionAst scriptBlockExpressionAst)) { return; } @@ -2336,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) @@ -2345,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 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; } - var candidateVarPath = varExpr.VariablePath; - if (candidateVarPath.UserPath.Equals(variableAstVariablePath.UserPath, StringComparison.OrdinalIgnoreCase)) + public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst) { - return true; + if (convertExpressionAst.IsRef() + && convertExpressionAst.Child is VariableExpressionAst varAst + && AssignsToTargetVar(varAst)) + { + SetLastAssignment(convertExpressionAst); + } + + return AstVisitAction.Continue; } - // The following condition is making an assumption that at script scope, we didn't use $script:, but in the local scope, we did - // If we are searching anything other than script scope, this is wrong. - if (variableAstVariablePath.IsScript && variableAstVariablePath.UnqualifiedPath.Equals(candidateVarPath.UnqualifiedPath, StringComparison.OrdinalIgnoreCase)) + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { - return true; + // 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; + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst == ScopeDefinitionAst + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + } + } + + internal static class TypeInferenceExtension + { + public static bool EqualsOrdinalIgnoreCase(this string s, string t) + { + return string.Equals(s, t, StringComparison.OrdinalIgnoreCase); + } + + public static IEnumerable GetGetterProperty(this Type type, string propertyName) + { + var res = new List(); + foreach (var m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + var name = m.Name; + // Equals without string allocation + if (name.Length == propertyName.Length + 4 + && name.StartsWith("get_") + && name.IndexOf(propertyName, 4, StringComparison.Ordinal) == 4) + { + res.Add(m); + } + } + + return res; + } + + public static bool IsDotsourced(this ScriptBlockExpressionAst scriptBlockExpressionAst) + { + Ast parent = scriptBlockExpressionAst.Parent; + + // This loop checks if the scriptblock is used as a dot sourced command + // or an argument for a command that uses the local scope eg: ForEach-Object -Process {$Var1 = "Hello"}, {Var2 = $true} + while (parent is not null) + { + if (parent is CommandAst cmdAst) + { + string cmdName = cmdAst.GetCommandName(); + return CompletionCompleters.s_localScopeCommandNames.Contains(cmdName) + || (cmdAst.CommandElements[0] is ScriptBlockExpressionAst && cmdAst.InvocationOperator == TokenKind.Dot); + } + + if (parent is not CommandExpressionAst and not PipelineAst and not StatementBlockAst and not ArrayExpressionAst and not ArrayLiteralAst) + { + break; + } + + parent = parent.Parent; } return false; diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs index 24c949960d1..73d44e94fbe 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 @@ -53,9 +54,9 @@ private static Type LookForTypeInSingleAssembly(Assembly assembly, string typena /// internal class AmbiguousTypeException : InvalidCastException { - public string[] Candidates { private set; get; } + public string[] Candidates { get; } - public TypeName TypeName { private set; get; } + public TypeName TypeName { get; } public AmbiguousTypeException(TypeName typeName, IEnumerable candidates) { @@ -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 { @@ -246,7 +250,7 @@ private static Type CallResolveTypeNameWorkerHelper(TypeName typeName, try { exception = null; - var currentScope = context != null ? context.EngineSessionState.CurrentScope : null; + var currentScope = context?.EngineSessionState.CurrentScope; Type result = ResolveTypeNameWorker(typeName, currentScope, typeResolutionState.assemblies, t_searchedAssemblies, typeResolutionState, /*onlySearchInGivenAssemblies*/ false, /* reportAmbiguousException */ true, out exception); if (exception == null && result == null) @@ -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) { @@ -615,9 +613,7 @@ public override bool Equals(object obj) if (object.ReferenceEquals(this, obj)) return true; - var other = obj as TypeResolutionState; - - if (other == null) + if (!(obj is TypeResolutionState other)) return false; if (this.attribute != other.attribute) @@ -673,9 +669,9 @@ public override int GetHashCode() } } - internal class TypeCache + internal static class TypeCache { - private class KeyComparer : IEqualityComparer> + private sealed class KeyComparer : IEqualityComparer> { public bool Equals(Tuple x, Tuple y) @@ -749,7 +745,7 @@ internal static class CoreTypes { typeof(Guid), new[] { "guid" } }, { typeof(Hashtable), new[] { "hashtable" } }, { typeof(int), new[] { "int", "int32" } }, - { typeof(Int16), new[] { "short", "int16" } }, + { typeof(short), new[] { "short", "int16" } }, { typeof(long), new[] { "long", "int64" } }, { typeof(CimInstance), new[] { "ciminstance" } }, { typeof(CimClass), new[] { "cimclass" } }, @@ -757,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" } }, @@ -780,15 +778,16 @@ internal static class CoreTypes { typeof(BigInteger), new[] { "bigint" } }, { typeof(SecureString), new[] { "securestring" } }, { typeof(TimeSpan), new[] { "timespan" } }, - { typeof(UInt16), new[] { "ushort", "uint16" } }, - { typeof(UInt32), new[] { "uint", "uint32" } }, - { typeof(UInt64), new[] { "ulong", "uint64" } }, + { typeof(ushort), new[] { "ushort", "uint16" } }, + { typeof(uint), new[] { "uint", "uint32" } }, + { typeof(ulong), new[] { "ulong", "uint64" } }, { typeof(Uri), new[] { "uri" } }, { typeof(ValidateCountAttribute), new[] { "ValidateCount" } }, { typeof(ValidateDriveAttribute), new[] { "ValidateDrive" } }, { 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" } }, @@ -935,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 052a4d5aa7f..8bf14d79aac 100644 --- a/src/System.Management.Automation/engine/parser/VariableAnalysis.cs +++ b/src/System.Management.Automation/engine/parser/VariableAnalysis.cs @@ -25,16 +25,23 @@ internal VariableAnalysisDetails() } public int BitIndex { get; set; } + public int LocalTupleIndex { get; set; } + public Type Type { get; set; } + public string Name { get; set; } + public bool Automatic { get; set; } + public bool PreferenceVariable { get; set; } + public bool Assigned { get; set; } - public List AssociatedAsts { get; private set; } + + public List AssociatedAsts { get; } } - internal class FindAllVariablesVisitor : AstVisitor + internal sealed class FindAllVariablesVisitor : AstVisitor { private static readonly HashSet s_hashOfPessimizingCmdlets = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -103,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; } @@ -178,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; } @@ -331,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) { @@ -340,14 +346,14 @@ internal LoopGotoTargets(string label, Block breakTarget, Block continueTarget) this.ContinueTarget = continueTarget; } - internal string Label { get; private set; } + internal string Label { get; } - internal Block BreakTarget { get; private set; } + internal Block BreakTarget { get; } - internal Block ContinueTarget { get; private set; } + internal Block ContinueTarget { get; } } - private class Block + private sealed class Block { internal readonly List _asts = new List(); private readonly List _successors = new List(); @@ -432,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; @@ -495,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; @@ -574,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) @@ -626,7 +632,7 @@ private Tuple> AnalyzeImpl(IParameterMetadataProvi var varName = GetUnaliasedVariableName(variablePath); var details = _variables[varName]; details.Assigned = true; - type = type ?? details.Type ?? typeof(object); + type ??= details.Type ?? typeof(object); // automatic and preference variables are pre-allocated, so they can't be unallocated // and forced to be dynamic. @@ -938,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); @@ -1310,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) @@ -1449,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; @@ -1635,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 7baec6102c3..ba5c48a2752 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -29,6 +29,8 @@ namespace System.Management.Automation.Language using System.Runtime.CompilerServices; using System.Reflection.Emit; +#nullable enable + internal interface ISupportsAssignment { IAssignableValue GetAssignableValue(); @@ -41,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 @@ -49,12 +51,16 @@ internal interface IAssignableValue /// Expression SetValue(Compiler compiler, Expression rhs); } +#nullable restore internal interface IParameterMetadataProvider { bool HasAnyScriptBlockAttributes(); + RuntimeDefinedParameterDictionary GetParameterMetadata(bool automaticPositions, ref bool usesCmdletBinding); + IEnumerable GetScriptBlockAttributes(); + IEnumerable GetExperimentalAttributes(); bool UsesCmdletBinding(); @@ -104,7 +110,7 @@ protected Ast(IScriptExtent extent) /// /// The extent in the source this ast represents. /// - public IScriptExtent Extent { get; private set; } + public IScriptExtent Extent { get; } /// /// The parent tree for this node. @@ -195,6 +201,19 @@ public override string ToString() /// If is deemed unsafe /// public object SafeGetValue() + { + return SafeGetValue(skipHashtableSizeCheck: false); + } + + /// + /// Constructs the resultant object from the AST and returns it if it is safe. + /// + /// Set to skip hashtable limit validation. + /// The object represented by the AST as a safe object. + /// + /// If is deemed unsafe. + /// + public object SafeGetValue(bool skipHashtableSizeCheck) { try { @@ -204,7 +223,7 @@ public object SafeGetValue() context = System.Management.Automation.Runspaces.Runspace.DefaultRunspace.ExecutionContext; } - return GetSafeValueVisitor.GetSafeValue(this, context, GetSafeValueVisitor.SafeValueContext.Default); + return GetSafeValueVisitor.GetSafeValue(this, context, skipHashtableSizeCheck ? GetSafeValueVisitor.SafeValueContext.SkipHashtableSizeCheck : GetSafeValueVisitor.SafeValueContext.Default); } catch { @@ -286,6 +305,7 @@ internal void ClearParent() } internal abstract object Accept(ICustomAstVisitor visitor); + internal abstract AstVisitAction InternalVisit(AstVisitor visitor); internal static readonly PSTypeName[] EmptyPSTypeNameArray = Array.Empty(); @@ -300,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) { @@ -368,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 FunctionMemberAst)) + if (ast is FunctionDefinitionAst functionDefinitionAst && functionDefinitionAst.Parent is not FunctionMemberAst) break; ast = ast.Parent; } @@ -501,10 +520,10 @@ 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; private set; } + public Token Kind { get; } /// /// The flags specified and their value. The value is null if it's not specified. @@ -514,24 +533,24 @@ internal ErrorStatementAst(IScriptExtent extent, Token kind, IEnumerable> Flags { get; private set; } + public Dictionary> Flags { get; } /// /// The conditions specified. /// - public ReadOnlyCollection Conditions { get; private set; } + public ReadOnlyCollection Conditions { get; } /// /// The bodies specified. /// - public ReadOnlyCollection Bodies { get; private set; } + public ReadOnlyCollection Bodies { get; } /// /// Sometimes a valid ast is parsed successfully within the extent that this error statement represents. Those /// asts are contained in this collection. This collection may contain other error asts. This collection may /// be null when no asts were successfully constructed within the extent of this error ast. /// - public ReadOnlyCollection NestedAst { get; private set; } + public ReadOnlyCollection NestedAst { get; } /// /// Copy the ErrorStatementAst instance. @@ -648,7 +667,7 @@ internal ErrorExpressionAst(IScriptExtent extent, IEnumerable nestedAsts = /// asts are contained in this collection. This collection may contain other error asts. This collection may /// be null when no asts were successfully constructed within the extent of this error ast. /// - public ReadOnlyCollection NestedAst { get; private set; } + public ReadOnlyCollection NestedAst { get; } /// /// Copy the ErrorExpressionAst instance. @@ -734,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 @@ -770,6 +781,7 @@ public class ScriptBlockAst : Ast, IParameterMetadataProvider Utils.EmptyReadOnlyCollection(); internal bool HadErrors { get; set; } + internal bool IsConfiguration { get; private set; } internal bool PostParseChecksPerformed { get; set; } @@ -778,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. @@ -797,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); @@ -835,6 +887,12 @@ public ScriptBlockAst(IScriptExtent extent, SetParent(endBlock); } + if (cleanBlock != null) + { + this.CleanBlock = cleanBlock; + SetParent(cleanBlock); + } + if (dynamicParamBlock != null) { this.DynamicParamBlock = dynamicParamBlock; @@ -846,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. @@ -867,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. /// @@ -890,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 @@ -953,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 @@ -995,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. /// @@ -1064,7 +1178,7 @@ private void SetUsingStatements(IEnumerable usingStatements) /// The asts for attributes (such as [DscLocalConfigurationManager()]) used before the scriptblock. /// This property is never null. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The asts for any using statements. This property is never null. @@ -1077,28 +1191,33 @@ private void SetUsingStatements(IEnumerable usingStatements) /// The ast representing the parameters for a script block, or null if no param block was specified. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param")] - public ParamBlockAst ParamBlock { get; private set; } + public ParamBlockAst ParamBlock { get; } /// /// The ast representing the begin block for a script block, or null if no begin block was specified. /// - public NamedBlockAst BeginBlock { get; private set; } + public NamedBlockAst BeginBlock { get; } /// /// The ast representing the process block for a script block, or null if no process block was specified. /// - public NamedBlockAst ProcessBlock { get; private set; } + public NamedBlockAst ProcessBlock { get; } /// /// The ast representing the end block for a script block, or null if no end block was specified. /// - public NamedBlockAst EndBlock { get; private set; } + 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. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Param")] - public NamedBlockAst DynamicParamBlock { get; private set; } + public NamedBlockAst DynamicParamBlock { get; } /// /// All of the parsed information from any #requires in the script, or null if #requires was not used. @@ -1173,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() @@ -1227,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; @@ -1237,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; @@ -1317,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) { @@ -1345,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 @@ -1414,7 +1550,7 @@ IEnumerable IParameterMetadataProvider.GetExperimentalAtt } } - ExperimentalAttribute GetExpAttributeHelper(AttributeAst attributeAst) + static ExperimentalAttribute GetExpAttributeHelper(AttributeAst attributeAst) { AttributeAst potentialExpAttr = null; string expAttrTypeName = typeof(ExperimentalAttribute).FullName; @@ -1492,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) @@ -1545,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); @@ -1559,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; } @@ -1581,7 +1718,7 @@ internal PipelineAst GetSimplePipeline(bool allowMultiplePipelines, out string e } // Make sure all statements are pipelines. - if (EndBlock.Statements.Any(ast => !(ast is PipelineAst))) + if (EndBlock.Statements.Any(ast => ast is not PipelineAst)) { errorId = "CanOnlyConvertOnePipeline"; errorMsg = AutomationExceptions.CanOnlyConvertOnePipeline; @@ -1649,12 +1786,12 @@ public ParamBlockAst(IScriptExtent extent, IEnumerable attributes, /// /// The asts for attributes (such as [cmdletbinding()]) used before the param keyword. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The asts for the parameters of the param statement. /// - public ReadOnlyCollection Parameters { get; private set; } + public ReadOnlyCollection Parameters { get; } /// /// Copy the ParamBlockAst instance. @@ -1727,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 @@ -1739,6 +1876,7 @@ public class NamedBlockAst : Ast /// /// /// + /// /// /// /// @@ -1749,7 +1887,7 @@ public class NamedBlockAst : Ast /// /// /// If is not one of the valid kinds for a named block, - /// or if is true and is neither + /// or if is and is neither /// nor . /// public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAst statementBlock, bool unnamed) @@ -1757,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)); } @@ -1795,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); @@ -1808,7 +1944,7 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs /// For a function/filter that did not explicitly name the end/process block (which is quite common), /// this property will return true. /// - public bool Unnamed { get; private set; } + public bool Unnamed { get; } /// /// The kind of block, always one of: @@ -1816,21 +1952,22 @@ public NamedBlockAst(IScriptExtent extent, TokenKind blockName, StatementBlockAs /// /// /// + /// /// /// /// - public TokenKind BlockKind { get; private set; } + public TokenKind BlockKind { get; } /// /// The asts for all of the statements represented by this statement block. This property is never null. /// - public ReadOnlyCollection Statements { get; private set; } + public ReadOnlyCollection Statements { get; } /// /// The asts for all of the trap statements specified by this statement block, or null if no trap statements were /// specified in this block. /// - public ReadOnlyCollection Traps { get; private set; } + public ReadOnlyCollection Traps { get; } /// /// Copy the NamedBlockAst instance. @@ -1855,10 +1992,18 @@ 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; private set; } + internal IScriptExtent OpenCurlyExtent { get; } - internal IScriptExtent CloseCurlyExtent { get; private set; } + internal IScriptExtent CloseCurlyExtent { get; } #region Visitors @@ -1925,18 +2070,18 @@ public NamedAttributeArgumentAst(IScriptExtent extent, string argumentName, Expr /// /// The named argument specified by this ast, is never null or empty. /// - public string ArgumentName { get; private set; } + public string ArgumentName { get; } /// /// The ast of the value of the argument specified by this ast. This property is never null. /// - public ExpressionAst Argument { get; private set; } + public ExpressionAst Argument { get; } /// /// If the source omitted an expression, this returns true, otherwise false. This allows a caller to distinguish /// the difference between [Parameter(Mandatory)] and [Parameter(Mandatory=$true)] /// - public bool ExpressionOmitted { get; private set; } + public bool ExpressionOmitted { get; } /// /// Copy the NamedAttributeArgumentAst instance. @@ -1995,7 +2140,7 @@ protected AttributeBaseAst(IScriptExtent extent, ITypeName typeName) /// /// The type name for the attribute. This property is never null. /// - public ITypeName TypeName { get; private set; } + public ITypeName TypeName { get; } internal abstract Attribute GetAttribute(); } @@ -2051,12 +2196,12 @@ public AttributeAst(IScriptExtent extent, /// /// The asts for the attribute arguments specified positionally. /// - public ReadOnlyCollection PositionalArguments { get; private set; } + public ReadOnlyCollection PositionalArguments { get; } /// /// The asts for the named attribute arguments. /// - public ReadOnlyCollection NamedArguments { get; private set; } + public ReadOnlyCollection NamedArguments { get; } /// /// Copy the AttributeAst instance. @@ -2223,17 +2368,17 @@ public ParameterAst(IScriptExtent extent, /// /// The asts for any attributes or type constraints specified on the parameter. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The variable path for the parameter. This property is never null. /// - public VariableExpressionAst Name { get; private set; } + public VariableExpressionAst Name { get; } /// /// The ast for the default value of the parameter, or null if no default value was specified. /// - public ExpressionAst DefaultValue { get; private set; } + public ExpressionAst DefaultValue { get; } /// /// Returns the type of the parameter. If the parameter is constrained to be a specific type, that type is returned, @@ -2308,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; @@ -2375,7 +2521,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) /// public class StatementBlockAst : Ast { - private static ReadOnlyCollection s_emptyStatementCollection = Utils.EmptyReadOnlyCollection(); + private static readonly ReadOnlyCollection s_emptyStatementCollection = Utils.EmptyReadOnlyCollection(); /// /// Construct a statement block. @@ -2411,13 +2557,13 @@ public StatementBlockAst(IScriptExtent extent, IEnumerable stateme /// /// The asts for all of the statements represented by this statement block. This property is never null. /// - public ReadOnlyCollection Statements { get; private set; } + public ReadOnlyCollection Statements { get; } /// /// The asts for all of the trap statements specified by this statement block, or null if no trap statements were /// specified in this block. /// - public ReadOnlyCollection Traps { get; private set; } + public ReadOnlyCollection Traps { get; } /// /// Copy the StatementBlockAst instance. @@ -2583,27 +2729,27 @@ public TypeDefinitionAst(IScriptExtent extent, string name, IEnumerable /// The name of the type. /// - public string Name { get; private set; } + public string Name { get; } /// /// The asts for the custom attributes specified on the type. This property is never null. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The asts for the base types. This property is never null. /// - public ReadOnlyCollection BaseTypes { get; private set; } + public ReadOnlyCollection BaseTypes { get; } /// /// The asts for the members of the type. This property is never null. /// - public ReadOnlyCollection Members { get; private set; } + public ReadOnlyCollection Members { get; } /// /// The type attributes (like class or interface) of the type. /// - public TypeAttributes TypeAttributes { get; private set; } + public TypeAttributes TypeAttributes { get; } /// /// Returns true if the type defines an enum. @@ -2656,14 +2802,13 @@ public override Ast Copy() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitTypeDefinition(this) : null; + return visitor2?.VisitTypeDefinition(this); } 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) @@ -2862,24 +3007,24 @@ public UsingStatementAst(IScriptExtent extent, StringConstantExpressionAst alias /// /// The kind of using statement. /// - public UsingStatementKind UsingStatementKind { get; private set; } + public UsingStatementKind UsingStatementKind { get; } /// /// When is null or is null, the item being used, otherwise the alias name. /// - public StringConstantExpressionAst Name { get; private set; } + public StringConstantExpressionAst Name { get; } /// /// The name of the item being aliased. /// This property is mutually exclusive with property. /// - public StringConstantExpressionAst Alias { get; private set; } + public StringConstantExpressionAst Alias { get; } /// /// Hashtable that can be converted to . Only for 'using module' case, otherwise null. /// This property is mutually exclusive with property. /// - public HashtableAst ModuleSpecification { get; private set; } + public HashtableAst ModuleSpecification { get; } /// /// ModuleInfo about used module. Only for 'using module' case, otherwise null. @@ -2904,14 +3049,13 @@ public override Ast Copy() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitUsingStatement(this) : null; + return visitor2?.VisitUsingStatement(this); } 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) @@ -3071,22 +3215,22 @@ public PropertyMemberAst(IScriptExtent extent, string name, TypeConstraintAst pr /// /// The ast for the type of the property. This property may be null if no type was specified. /// - public TypeConstraintAst PropertyType { get; private set; } + public TypeConstraintAst PropertyType { get; } /// /// The custom attributes of the property. This property is never null. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The attributes (like public or static) of the property. /// - public PropertyAttributes PropertyAttributes { get; private set; } + public PropertyAttributes PropertyAttributes { get; } /// /// The ast for the initial value of the property. This property may be null if no initial value was specified. /// - public ExpressionAst InitialValue { get; private set; } + public ExpressionAst InitialValue { get; } /// /// Return true if the property is public. @@ -3132,14 +3276,13 @@ internal override string GetTooltip() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitPropertyMember(this) : null; + return visitor2?.VisitPropertyMember(this); } 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) @@ -3154,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; + } } } @@ -3254,12 +3400,12 @@ public FunctionMemberAst(IScriptExtent extent, FunctionDefinitionAst functionDef /// /// The attributes specified on the method. This property is never null. /// - public ReadOnlyCollection Attributes { get; private set; } + public ReadOnlyCollection Attributes { get; } /// /// The ast representing the return type for the method. This property may be null if no return type was specified. /// - public TypeConstraintAst ReturnType { get; private set; } + public TypeConstraintAst ReturnType { get; } /// /// The parameters specified immediately after the function name. This property is never null. @@ -3277,7 +3423,7 @@ public ReadOnlyCollection Parameters /// /// Method attribute flags. /// - public MethodAttributes MethodAttributes { get; private set; } + public MethodAttributes MethodAttributes { get; } /// /// Returns true if the method is public. @@ -3309,6 +3455,8 @@ public bool IsConstructor internal IScriptExtent NameExtent { get { return _functionDefinitionAst.NameExtent; } } + private string _toolTip; + /// /// Copy a function member ast. /// @@ -3323,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) { - sb.Append(", "); + continue; } - sb.Append(Parameters[i].GetTooltip()); + if (sb.Length > 0) + { + sb.AppendLine(); + } + + 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 @@ -3352,14 +3528,13 @@ internal override string GetTooltip() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitFunctionMember(this) : null; + return visitor2?.VisitFunctionMember(this); } 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) @@ -3370,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; + } } } @@ -3444,7 +3622,7 @@ internal bool IsReturnTypeVoid() if (ReturnType == null) return true; var typeName = ReturnType.TypeName as TypeName; - return typeName == null ? false : typeName.IsType(typeof(void)); + return typeName != null && typeName.IsType(typeof(void)); } internal Type GetReturnType() @@ -3486,9 +3664,9 @@ public override string Name get { return DefiningType.Name; } } - internal TypeDefinitionAst DefiningType { get; private set; } + internal TypeDefinitionAst DefiningType { get; } - internal SpecialMemberFunctionType Type { get; private set; } + internal SpecialMemberFunctionType Type { get; } internal override string GetTooltip() { @@ -3539,7 +3717,7 @@ public bool UsesCmdletBinding() public ReadOnlyCollection Parameters { get { return null; } } - public ScriptBlockAst Body { get; private set; } + public ScriptBlockAst Body { get; } public PowerShell GetPowerShell(ExecutionContext context, Dictionary variables, bool isTrustedInput, bool filterNonUsingVariables, bool? createLocalScope, params object[] args) @@ -3639,17 +3817,17 @@ internal FunctionDefinitionAst(IScriptExtent extent, /// /// If true, the filter keyword was used. /// - public bool IsFilter { get; private set; } + public bool IsFilter { get; } /// /// If true, the workflow keyword was used. /// - public bool IsWorkflow { get; private set; } + public bool IsWorkflow { get; } /// /// The name of the function or filter. This property is never null or empty. /// - public string Name { get; private set; } + public string Name { get; } /// /// The parameters specified immediately after the function name, or null if no parameters were specified. @@ -3660,12 +3838,12 @@ internal FunctionDefinitionAst(IScriptExtent extent, /// In this example, the parameters specified after the function name must be empty or the script is not valid. /// /// - public ReadOnlyCollection Parameters { get; private set; } + public ReadOnlyCollection Parameters { get; } /// /// The body of the function. This property is never null. /// - public ScriptBlockAst Body { get; private set; } + public ScriptBlockAst Body { get; } internal IScriptExtent NameExtent { get; private set; } @@ -3696,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) @@ -3732,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; } @@ -3756,7 +3931,7 @@ internal string GetParamTextFromParameterList(Tuple, separator = ", "; } - sb.Append(")"); + sb.Append(')'); sb.Append(Environment.NewLine); return sb.ToString(); @@ -3829,7 +4004,7 @@ IEnumerable IParameterMetadataProvider.GetExperimentalAtt ReadOnlyCollection IParameterMetadataProvider.Parameters { - get { return Parameters ?? (Body.ParamBlock != null ? Body.ParamBlock.Parameters : null); } + get { return Parameters ?? (Body.ParamBlock?.Parameters); } } PowerShell IParameterMetadataProvider.GetPowerShell(ExecutionContext context, Dictionary variables, bool isTrustedInput, @@ -3920,12 +4095,12 @@ public IfStatementAst(IScriptExtent extent, IEnumerable clauses, State /// executed. This property is never null and always has at least 1 value. /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] - public ReadOnlyCollection Clauses { get; private set; } + public ReadOnlyCollection Clauses { get; } /// /// The ast for the else clause, or null if no else clause is specified. /// - public StatementBlockAst ElseClause { get; private set; } + public StatementBlockAst ElseClause { get; } /// /// Copy the IfStatementAst instance. @@ -4019,7 +4194,7 @@ public DataStatementAst(IScriptExtent extent, { this.CommandsAllowed = new ReadOnlyCollection(commandsAllowed.ToArray()); SetParents(CommandsAllowed); - this.HasNonConstantAllowedCommand = CommandsAllowed.Any(ast => !(ast is StringConstantExpressionAst)); + this.HasNonConstantAllowedCommand = CommandsAllowed.Any(static ast => ast is not StringConstantExpressionAst); } else { @@ -4033,17 +4208,17 @@ public DataStatementAst(IScriptExtent extent, /// /// The name of the variable this data statement sets, or null if no variable name was specified. /// - public string Variable { get; private set; } + public string Variable { get; } /// /// The asts naming the commands allowed to execute in this data statement. /// - public ReadOnlyCollection CommandsAllowed { get; private set; } + public ReadOnlyCollection CommandsAllowed { get; } /// /// The ast for the body of the data statement. This property is never null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } /// /// Copy the DataStatementAst instance. @@ -4055,7 +4230,7 @@ public override Ast Copy() return new DataStatementAst(this.Extent, this.Variable, newCommandsAllowed, newBody); } - internal bool HasNonConstantAllowedCommand { get; private set; } + internal bool HasNonConstantAllowedCommand { get; } #region Visitors @@ -4118,13 +4293,13 @@ protected LabeledStatementAst(IScriptExtent extent, string label, PipelineBaseAs /// /// The label name if specified, otherwise null. /// - public string Label { get; private set; } + public string Label { get; } /// /// The ast for the condition that is tested on each iteration of the loop, or the condition tested on a switch. /// This property may be null if the statement is a , otherwise it is never null. /// - public PipelineBaseAst Condition { get; private set; } + public PipelineBaseAst Condition { get; } } /// @@ -4158,7 +4333,7 @@ protected LoopStatementAst(IScriptExtent extent, string label, PipelineBaseAst c /// /// The body of a loop statement. This property is never null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } } /// @@ -4253,17 +4428,17 @@ public ForEachStatementAst(IScriptExtent extent, /// /// The name of the variable set for each item as the loop iterates. This property is never null. /// - public VariableExpressionAst Variable { get; private set; } + public VariableExpressionAst Variable { get; } /// /// The limit to be obeyed during parallel processing, if any. /// - public ExpressionAst ThrottleLimit { get; private set; } + public ExpressionAst ThrottleLimit { get; } /// /// The flags, if any specified on the foreach statement. /// - public ForEachFlags Flags { get; private set; } + public ForEachFlags Flags { get; } /// /// Copy the ForEachStatementAst instance. @@ -4349,12 +4524,12 @@ public ForStatementAst(IScriptExtent extent, /// /// The ast for the initialization expression of a for statement, or null if none was specified. /// - public PipelineBaseAst Initializer { get; private set; } + public PipelineBaseAst Initializer { get; } /// /// The ast for the iteration expression of a for statement, or null if none was specified. /// - public PipelineBaseAst Iterator { get; private set; } + public PipelineBaseAst Iterator { get; } /// /// Copy the ForStatementAst instance. @@ -4403,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. @@ -4664,19 +4839,19 @@ public SwitchStatementAst(IScriptExtent extent, /// /// The flags, if any specified on the switch statement. /// - public SwitchFlags Flags { get; private set; } + public SwitchFlags Flags { get; } /// /// A possibly empty collection of conditions and statement blocks representing the cases of the switch statement. /// If the collection is empty, the default clause is not null. /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] - public ReadOnlyCollection Clauses { get; private set; } + public ReadOnlyCollection Clauses { get; } /// /// The ast for the default of the switch statement, or null if no default block was specified. /// - public StatementBlockAst Default { get; private set; } + public StatementBlockAst Default { get; } /// /// Copy the SwitchStatementAst instance. @@ -4784,7 +4959,7 @@ public CatchClauseAst(IScriptExtent extent, IEnumerable catch /// A possibly empty collection of types caught by this catch block. If the collection is empty, the catch handler /// catches all exceptions. /// - public ReadOnlyCollection CatchTypes { get; private set; } + public ReadOnlyCollection CatchTypes { get; } /// /// Returns true if this handler handles any kind of exception. @@ -4795,7 +4970,7 @@ public CatchClauseAst(IScriptExtent extent, IEnumerable catch /// /// The body of the catch block. This property is never null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } /// /// Copy the CatchClauseAst instance. @@ -4897,18 +5072,18 @@ public TryStatementAst(IScriptExtent extent, /// /// The body of the try statement. This property is never null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } /// /// A collection of catch clauses, which is empty if there are no catches. /// - public ReadOnlyCollection CatchClauses { get; private set; } + public ReadOnlyCollection CatchClauses { get; } /// /// The ast for the finally block, or null if no finally block was specified, in which case /// is a non-null, non-empty collection. /// - public StatementBlockAst Finally { get; private set; } + public StatementBlockAst Finally { get; } /// /// Copy the TryStatementAst instance. @@ -4991,12 +5166,12 @@ public TrapStatementAst(IScriptExtent extent, TypeConstraintAst trapType, Statem /// /// The ast for the type trapped by this trap block, or null if no type was specified. /// - public TypeConstraintAst TrapType { get; private set; } + public TypeConstraintAst TrapType { get; } /// /// The body for the trap block. This property is never null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } /// /// Copy the TrapStatementAst instance. @@ -5060,7 +5235,7 @@ public BreakStatementAst(IScriptExtent extent, ExpressionAst label) /// /// The expression or label to break to, or null if no label was specified. /// - public ExpressionAst Label { get; private set; } + public ExpressionAst Label { get; } /// /// Copy the BreakStatementAst instance. @@ -5117,7 +5292,7 @@ public ContinueStatementAst(IScriptExtent extent, ExpressionAst label) /// /// The expression or label to continue to, or null if no label was specified. /// - public ExpressionAst Label { get; private set; } + public ExpressionAst Label { get; } /// /// Copy the ContinueStatementAst instance. @@ -5174,7 +5349,7 @@ public ReturnStatementAst(IScriptExtent extent, PipelineBaseAst pipeline) /// /// The pipeline specified in the return statement, or null if none was specified. /// - public PipelineBaseAst Pipeline { get; private set; } + public PipelineBaseAst Pipeline { get; } /// /// Copy the ReturnStatementAst instance. @@ -5231,7 +5406,7 @@ public ExitStatementAst(IScriptExtent extent, PipelineBaseAst pipeline) /// /// The pipeline specified in the exit statement, or null if none was specified. /// - public PipelineBaseAst Pipeline { get; private set; } + public PipelineBaseAst Pipeline { get; } /// /// Copy the ExitStatementAst instance. @@ -5288,7 +5463,7 @@ public ThrowStatementAst(IScriptExtent extent, PipelineBaseAst pipeline) /// /// The pipeline specified in the throw statement, or null if none was specified. /// - public PipelineBaseAst Pipeline { get; private set; } + public PipelineBaseAst Pipeline { get; } /// /// If the throw statement is a rethrow. In PowerShell, a throw statement need not throw anything. Such @@ -5389,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) { @@ -5591,7 +5760,7 @@ public PipelineAst(IScriptExtent extent, CommandBaseAst commandAst) : this(exten /// /// A non-null, non-empty collection of commands that represent the pipeline. /// - public ReadOnlyCollection PipelineElements { get; private set; } + public ReadOnlyCollection PipelineElements { get; } /// /// Indicates that this pipeline should be run in the background. @@ -5728,19 +5897,19 @@ public CommandParameterAst(IScriptExtent extent, string parameterName, Expressio /// The name of the parameter. This value does not include a leading dash, and in the case that an argument /// is specified, no trailing colon is included either. This property is never null or empty. /// - public string ParameterName { get; private set; } + public string ParameterName { get; } /// /// The ast for the argument if specified (e.g. -Path:-abc, then the argument is the ast for '-ast'), otherwise null /// if no argument was specified. /// - public ExpressionAst Argument { get; private set; } + public ExpressionAst Argument { get; } /// /// The error position to use when parameter binding fails. This extent does not include the argument if one was /// specified, which means this extent is often the same as . /// - public IScriptExtent ErrorPosition { get; private set; } + public IScriptExtent ErrorPosition { get; } /// /// Copy the CommandParameterAst instance. @@ -5809,7 +5978,7 @@ protected CommandBaseAst(IScriptExtent extent, IEnumerable redir /// /// The possibly empty collection of redirections for this command. /// - public ReadOnlyCollection Redirections { get; private set; } + public ReadOnlyCollection Redirections { get; } } /// @@ -5857,20 +6026,20 @@ public CommandAst(IScriptExtent extent, /// /// A non-empty collection of command elements. This property is never null. /// - public ReadOnlyCollection CommandElements { get; private set; } + public ReadOnlyCollection CommandElements { get; } /// /// The invocation operator (either or ) if one was specified, /// otherwise the value is . /// - public TokenKind InvocationOperator { get; private set; } + public TokenKind InvocationOperator { get; } /// /// 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. /// @@ -5879,7 +6048,7 @@ public CommandAst(IScriptExtent extent, public string GetCommandName() { var name = CommandElements[0] as StringConstantExpressionAst; - return name != null ? name.Value : null; + return name?.Value; } /// @@ -5972,7 +6141,7 @@ public CommandExpressionAst(IScriptExtent extent, /// /// The ast for the expression that is or starts a pipeline. This property is never null. /// - public ExpressionAst Expression { get; private set; } + public ExpressionAst Expression { get; } /// /// Copy the CommandExpressionAst instance. @@ -6035,7 +6204,7 @@ protected RedirectionAst(IScriptExtent extent, RedirectionStream from) /// /// The stream to read objects from. Objects are either merged with another stream, or written to a file. /// - public RedirectionStream FromStream { get; private set; } + public RedirectionStream FromStream { get; } } /// @@ -6101,7 +6270,7 @@ public MergingRedirectionAst(IScriptExtent extent, RedirectionStream from, Redir /// /// The stream that results will be written to. /// - public RedirectionStream ToStream { get; private set; } + public RedirectionStream ToStream { get; } /// /// Copy the MergingRedirectionAst instance. @@ -6166,12 +6335,12 @@ public FileRedirectionAst(IScriptExtent extent, RedirectionStream stream, Expres /// /// The ast for the location to redirect to. /// - public ExpressionAst Location { get; private set; } + public ExpressionAst Location { get; } /// /// True if the file is appended, false otherwise. /// - public bool Append { get; private set; } + public bool Append { get; } /// /// Copy the FileRedirectionAst instance. @@ -6236,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; @@ -6262,22 +6425,22 @@ public AssignmentStatementAst(IScriptExtent extent, ExpressionAst left, TokenKin /// /// The ast for the location being assigned. This property is never null. /// - public ExpressionAst Left { get; private set; } + public ExpressionAst Left { get; } /// /// The operator for token assignment (such as =, +=, -=, etc.). The value is always some assignment operator. /// - public TokenKind Operator { get; private set; } + public TokenKind Operator { get; } /// /// The ast for the value to assign. This property is never null. /// - public StatementAst Right { get; private set; } + public StatementAst Right { get; } /// /// The position to report at runtime if there is an error during assignment. This property is never null. /// - public IScriptExtent ErrorPosition { get; private set; } + public IScriptExtent ErrorPosition { get; } /// /// Copy the AssignmentStatementAst instance. @@ -6297,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) { @@ -6396,19 +6558,19 @@ public ConfigurationDefinitionAst(IScriptExtent extent, /// This ast represents configuration body script block. /// This property is never null. /// - public ScriptBlockExpressionAst Body { get; private set; } + public ScriptBlockExpressionAst Body { get; } /// /// The configuration type. /// - public ConfigurationType ConfigurationType { get; private set; } + public ConfigurationType ConfigurationType { get; } /// /// The name of the configuration instance, /// For example, Instance name of 'configuration test { ...... }' is 'test' /// This property is never null. /// - public ExpressionAst InstanceName { get; private set; } + public ExpressionAst InstanceName { get; } /// /// Duplicates the , allowing it to be composed into other ASTs. @@ -6422,7 +6584,7 @@ public override Ast Copy() { LCurlyToken = this.LCurlyToken, ConfigurationToken = this.ConfigurationToken, - CustomAttributes = this.CustomAttributes == null ? null : this.CustomAttributes.Select(e => (AttributeAst)e.Copy()) + CustomAttributes = this.CustomAttributes?.Select(static e => (AttributeAst)e.Copy()) }; } @@ -6431,14 +6593,13 @@ public override Ast Copy() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitConfigurationDefinition(this) : null; + return visitor2?.VisitConfigurationDefinition(this); } 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) @@ -6463,7 +6624,9 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) #region Internal methods/properties internal Token LCurlyToken { get; set; } + internal Token ConfigurationToken { get; set; } + internal IEnumerable CustomAttributes { get; set; } /// @@ -6521,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 == null ? null : CustomAttributes.Select(att => (AttributeAst)att.Copy()).ToList(), + CustomAttributes?.Select(static att => (AttributeAst)att.Copy()).ToList(), null, new StatementBlockAst(Body.Extent, resourceBody, null), false, false); @@ -6554,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 @@ -6567,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); @@ -6575,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 == null ? null : 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); @@ -6872,7 +7035,7 @@ public DynamicKeywordStatementAst(IScriptExtent extent, /// /// This property is never null and never empty. /// - public ReadOnlyCollection CommandElements { get; private set; } + public ReadOnlyCollection CommandElements { get; } /// /// Duplicates the , allowing it to be composed into other ASTs. @@ -6898,14 +7061,13 @@ public override Ast Copy() internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitDynamicKeywordStatement(this) : null; + return visitor2?.VisitDynamicKeywordStatement(this); } 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) @@ -6945,10 +7107,15 @@ internal DynamicKeyword Keyword private DynamicKeyword _keyword; internal Token LCurly { get; set; } + internal Token FunctionName { get; set; } + internal ExpressionAst InstanceName { get; set; } + internal ExpressionAst OriginalInstanceName { get; set; } + internal ExpressionAst BodyExpression { get; set; } + internal string ElementName { get; set; } private PipelineAst _commandCallPipelineAst; @@ -7002,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 @@ -7021,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; // @@ -7097,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", @@ -7205,16 +7371,12 @@ 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(); } - var commandExpr = this.Parent as CommandExpressionAst; - if (commandExpr == null) + if (!(this.Parent is CommandExpressionAst commandExpr)) { return false; } @@ -7225,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(); } @@ -7279,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); @@ -7381,22 +7542,22 @@ public BinaryExpressionAst(IScriptExtent extent, ExpressionAst left, TokenKind @ /// /// The operator token kind. The value returned is always a binary operator. /// - public TokenKind Operator { get; private set; } + public TokenKind Operator { get; } /// /// The ast for the left hand side of the binary expression. The property is never null. /// - public ExpressionAst Left { get; private set; } + public ExpressionAst Left { get; } /// /// The ast for the right hand side of the binary expression. The property is never null. /// - public ExpressionAst Right { get; private set; } + public ExpressionAst Right { get; } /// /// The position to report an error if an error occurs at runtime. The property is never null. /// - public IScriptExtent ErrorPosition { get; private set; } + public IScriptExtent ErrorPosition { get; } /// /// Copy the BinaryExpressionAst instance. @@ -7430,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 @@ -7491,12 +7654,12 @@ public UnaryExpressionAst(IScriptExtent extent, TokenKind tokenKind, ExpressionA /// /// The operator token for the unary expression. The value returned is always a unary operator. /// - public TokenKind TokenKind { get; private set; } + public TokenKind TokenKind { get; } /// /// The child expression the unary operator is applied to. The property is never null. /// - public ExpressionAst Child { get; private set; } + public ExpressionAst Child { get; } /// /// Copy the UnaryExpressionAst instance. @@ -7573,12 +7736,12 @@ public BlockStatementAst(IScriptExtent extent, Token kind, StatementBlockAst bod /// /// The scriptblockexpression that has a keyword applied to it. This property is nerver null. /// - public StatementBlockAst Body { get; private set; } + public StatementBlockAst Body { get; } /// /// The keyword name. /// - public Token Kind { get; private set; } + public Token Kind { get; } /// /// Copy the BlockStatementAst instance. @@ -7643,12 +7806,12 @@ public AttributedExpressionAst(IScriptExtent extent, AttributeBaseAst attribute, /// /// The expression that has an attribute or type constraint applied to it. This property is never null. /// - public ExpressionAst Child { get; private set; } + public ExpressionAst Child { get; } /// /// The attribute or type constraint for this expression. This property is never null. /// - public AttributeBaseAst Attribute { get; private set; } + public AttributeBaseAst Attribute { get; } /// /// Copy the AttributedExpressionAst instance. @@ -7726,8 +7889,7 @@ Expression IAssignableValue.SetValue(Compiler compiler, Expression rhs) var attributes = GetAttributes(); var assignableValue = GetActualAssignableAst().GetAssignableValue(); - var variableExpr = assignableValue as VariableExpressionAst; - if (variableExpr == null) + if (!(assignableValue is VariableExpressionAst variableExpr)) { return assignableValue.SetValue(compiler, Compiler.ConvertValue(rhs, attributes)); } @@ -7777,7 +7939,7 @@ public override Ast Copy() /// /// The static type produced after the cast is normally the type named by , but in some cases - /// it may not be, in which, is assumed. + /// it may not be, in which, is assumed. /// public override Type StaticType { @@ -7809,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 @@ -7836,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 @@ -7888,17 +8049,17 @@ public MemberExpressionAst(IScriptExtent extent, ExpressionAst expression, Comma /// /// The expression that produces the value to retrieve the member from. This property is never null. /// - public ExpressionAst Expression { get; private set; } + public ExpressionAst Expression { get; } /// /// The name of the member to retrieve. This property is never null. /// - public CommandElementAst Member { get; private set; } + public CommandElementAst Member { get; } /// /// True if the member to return is static, false if the member is an instance member. /// - public bool Static { get; private set; } + public bool Static { get; } /// /// Gets a value indicating true if the operator used is ?. or ?[]. @@ -7912,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 @@ -7948,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 @@ -7960,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()) @@ -7971,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) + { } /// @@ -7987,19 +8192,60 @@ 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. /// - public ReadOnlyCollection Arguments { get; private set; } + public ReadOnlyCollection Arguments { get; } /// /// Copy the InvokeMemberExpressionAst instance. @@ -8009,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 @@ -8089,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) @@ -8105,13 +8358,14 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) internal override object Accept(ICustomAstVisitor visitor) { var visitor2 = visitor as ICustomAstVisitor2; - return visitor2 != null ? visitor2.VisitBaseCtorInvokeMemberExpression(this) : null; + return visitor2?.VisitBaseCtorInvokeMemberExpression(this); } } /// /// The name and attributes of a type. /// +#nullable enable public interface ITypeName { /// @@ -8127,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. @@ -8142,34 +8396,39 @@ 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 { - Type CachedType { get; set; } + Type? CachedType { get; set; } } +#nullable restore /// /// A simple type that is not an array or does not have generic arguments. /// 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; /// @@ -8196,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("``", "`"); } @@ -8229,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. /// @@ -8276,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) { @@ -8306,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 @@ -8342,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)) { @@ -8371,8 +8666,7 @@ public override string ToString() /// public override bool Equals(object obj) { - var other = obj as TypeName; - if (other == null) + if (!(obj is TypeName other)) return false; if (!_name.Equals(other._name, StringComparison.OrdinalIgnoreCase)) @@ -8562,12 +8856,12 @@ public string Name /// /// The typename that specifies the generic class. /// - public ITypeName TypeName { get; private set; } + public ITypeName TypeName { get; } /// /// The generic arguments for this typename. /// - public ReadOnlyCollection GenericArguments { get; private set; } + public ReadOnlyCollection GenericArguments { get; } /// /// The extent of the typename. @@ -8636,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(); } } @@ -8663,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(); } } @@ -8690,8 +8994,7 @@ public override string ToString() /// public override bool Equals(object obj) { - var other = obj as GenericTypeName; - if (other == null) + if (!(obj is GenericTypeName other)) return false; if (!TypeName.Equals(other.TypeName)) @@ -8843,12 +9146,12 @@ public string Name /// /// The element type of the array. /// - public ITypeName ElementType { get; private set; } + public ITypeName ElementType { get; } /// /// The rank of the array. /// - public int Rank { get; private set; } + public int Rank { get; } /// /// The extent of the typename. @@ -8916,8 +9219,7 @@ public override string ToString() /// public override bool Equals(object obj) { - var other = obj as ArrayTypeName; - if (other == null) + if (!(obj is ArrayTypeName other)) return false; return ElementType.Equals(other.ElementType) && Rank == other.Rank; @@ -9018,8 +9320,7 @@ public override string ToString() /// public override bool Equals(object obj) { - var other = obj as ReflectionTypeName; - if (other == null) + if (!(obj is ReflectionTypeName other)) return false; return _type == other._type; } @@ -9065,7 +9366,7 @@ public TypeExpressionAst(IScriptExtent extent, ITypeName typeName) /// /// The name of the type. This property is never null. /// - public ITypeName TypeName { get; private set; } + public ITypeName TypeName { get; } /// /// Copy the TypeExpressionAst instance. @@ -9158,13 +9459,13 @@ public VariableExpressionAst(IScriptExtent extent, VariablePath variablePath, bo /// /// The name of the variable. This property is never null. /// - public VariablePath VariablePath { get; private set; } + public VariablePath VariablePath { get; } /// /// True if splatting syntax was used, false otherwise. /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] - public bool Splatted { get; private set; } + public bool Splatted { get; } /// /// Check if the variable is one of $true, $false and $null. @@ -9239,6 +9540,7 @@ internal override AstVisitAction InternalVisit(AstVisitor visitor) internal int TupleIndex { get; set; } = VariableAnalysis.Unanalyzed; internal bool Automatic { get; set; } + internal bool Assigned { get; set; } IAssignableValue ISupportsAssignment.GetAssignableValue() @@ -9351,7 +9653,7 @@ internal ConstantExpressionAst(NumberToken token) /// /// The value of the constant. This property is null only if the expression represents the null constant. /// - public object Value { get; private set; } + public object Value { get; } /// /// Copy the ConstantExpressionAst instance. @@ -9458,7 +9760,7 @@ internal StringConstantExpressionAst(StringToken token) /// /// The type of string. /// - public StringConstantType StringConstantType { get; private set; } + public StringConstantType StringConstantType { get; } /// /// The value of the string, not including the quotes used. @@ -9553,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; @@ -9609,18 +9910,18 @@ private ExpandableStringExpressionAst(IScriptExtent extent, string value, string /// The value of string, not including the quote characters and without any variables replaced. /// This property is never null. /// - public string Value { get; private set; } + public string Value { get; } /// /// The type of string. /// - public StringConstantType StringConstantType { get; private set; } + public StringConstantType StringConstantType { get; } /// /// A non-empty collection of expressions contained within the string. The nested expressions are always either /// instances of or . /// - public ReadOnlyCollection NestedExpressions { get; private set; } + public ReadOnlyCollection NestedExpressions { get; } /// /// Copy the ExpandableStringExpressionAst instance. @@ -9642,7 +9943,7 @@ public override Type StaticType /// /// The format expression needed to execute this ast. It is generated by the scanner, it is not provided by clients. /// - internal string FormatExpression { get; private set; } + internal string FormatExpression { get; } #region Visitors @@ -9700,7 +10001,7 @@ public ScriptBlockExpressionAst(IScriptExtent extent, ScriptBlockAst scriptBlock /// /// The ast for the scriptblock that this ast represent. This property is never null. /// - public ScriptBlockAst ScriptBlock { get; private set; } + public ScriptBlockAst ScriptBlock { get; } /// /// Copy the ScriptBlockExpressionAst instance. @@ -9772,7 +10073,7 @@ public ArrayLiteralAst(IScriptExtent extent, IList elements) /// /// The non-empty collection of asts of the elements of the array. /// - public ReadOnlyCollection Elements { get; private set; } + public ReadOnlyCollection Elements { get; } /// /// Copy the ArrayLiteralAst instance. @@ -9855,7 +10156,7 @@ public HashtableAst(IScriptExtent extent, IEnumerable keyValuePair /// The pairs of key names and asts for values used to construct the hash table. /// [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] - public ReadOnlyCollection KeyValuePairs { get; private set; } + public ReadOnlyCollection KeyValuePairs { get; } /// /// Copy the HashtableAst instance. @@ -9946,7 +10247,7 @@ public ArrayExpressionAst(IScriptExtent extent, StatementBlockAst statementBlock /// The expression/statements represented by this sub-expression. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] - public StatementBlockAst SubExpression { get; private set; } + public StatementBlockAst SubExpression { get; } /// /// Copy the ArrayExpressionAst instance. @@ -10012,7 +10313,7 @@ public ParenExpressionAst(IScriptExtent extent, PipelineBaseAst pipeline) /// The pipeline (which is frequently but not always an expression) for this parenthesized expression. /// This property is never null. /// - public PipelineBaseAst Pipeline { get; private set; } + public PipelineBaseAst Pipeline { get; } /// /// Copy the ParenExpressionAst instance. @@ -10078,7 +10379,7 @@ public SubExpressionAst(IScriptExtent extent, StatementBlockAst statementBlock) /// The expression/statements represented by this sub-expression. This property is never null. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] - public StatementBlockAst SubExpression { get; private set; } + public StatementBlockAst SubExpression { get; } /// /// Copy the SubExpressionAst instance. @@ -10139,7 +10440,7 @@ public UsingExpressionAst(IScriptExtent extent, ExpressionAst expressionAst) /// The expression represented by this using expression. This property is never null. /// [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] - public ExpressionAst SubExpression { get; private set; } + public ExpressionAst SubExpression { get; } // Used from code gen to get the value from a well known location. internal int RuntimeUsingIndex @@ -10175,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); } @@ -10190,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) @@ -10204,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) @@ -10216,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) @@ -10301,17 +10596,17 @@ public IndexExpressionAst(IScriptExtent extent, ExpressionAst target, Expression /// /// Return the ast for the expression being indexed. This value is never null. /// - public ExpressionAst Target { get; private set; } + public ExpressionAst Target { get; } /// /// Return the ast for the index expression. This value is never null. /// - public ExpressionAst Index { get; private set; } + public ExpressionAst Index { get; } /// /// Gets a value indicating whether ?[] operator is being used. /// - public bool NullConditional { get; private set; } + public bool NullConditional { get; } /// /// Copy the IndexExpressionAst instance. diff --git a/src/System.Management.Automation/engine/parser/token.cs b/src/System.Management.Automation/engine/parser/token.cs index 3f78e299a88..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); } } @@ -1349,12 +1367,17 @@ internal VariableToken(InternalScriptExtent scriptExtent, VariablePath path, Tok /// /// The full details of the variable path. /// - public VariablePath VariablePath { get; private set; } + public VariablePath VariablePath { get; } 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); } } @@ -1503,12 +1531,12 @@ internal MergingRedirectionToken(InternalScriptExtent scriptExtent, RedirectionS /// /// The stream being redirected. /// - public RedirectionStream FromStream { get; private set; } + public RedirectionStream FromStream { get; } /// /// The stream being written to. /// - public RedirectionStream ToStream { get; private set; } + public RedirectionStream ToStream { get; } } /// @@ -1526,12 +1554,12 @@ internal FileRedirectionToken(InternalScriptExtent scriptExtent, RedirectionStre /// /// The stream being redirected. /// - public RedirectionStream FromStream { get; private set; } + public RedirectionStream FromStream { get; } /// /// True if the redirection should append the file rather than create a new file. /// - public bool Append { get; private set; } + public bool Append { get; } } internal class UnscannedSubExprToken : StringLiteralToken @@ -1542,6 +1570,6 @@ internal UnscannedSubExprToken(InternalScriptExtent scriptExtent, TokenFlags tok this.SkippedCharOffsets = skippedCharOffsets; } - internal BitArray SkippedCharOffsets { get; private set; } + internal BitArray SkippedCharOffsets { get; } } } diff --git a/src/System.Management.Automation/engine/parser/tokenizer.cs b/src/System.Management.Automation/engine/parser/tokenizer.cs index 2b574071e1e..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 @@ -41,7 +43,7 @@ public enum DynamicKeywordNameMode /// Name may be optionally present, expression or bare word. /// OptionalName = 4, - }; + } /// /// Defines the body mode for a dynamic keyword. It can be a scriptblock, hashtable or command which means no body. @@ -77,8 +79,7 @@ private static Dictionary DynamicKeywords { get { - return t_dynamicKeywords ?? - (t_dynamicKeywords = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return t_dynamicKeywords ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } @@ -92,8 +93,7 @@ private static Stack> DynamicKeywordsStack { get { - return t_dynamicKeywordsStack ?? - (t_dynamicKeywordsStack = new Stack>()); + return t_dynamicKeywordsStack ??= new Stack>(); } } @@ -314,28 +314,26 @@ 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 { get { - return _properties ?? - (_properties = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return _properties ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } private Dictionary _properties; /// - /// A list of the parameters allowed for this constuctor. + /// A list of the parameters allowed for this constructor. /// public Dictionary Parameters { get { - return _parameters ?? - (_parameters = new Dictionary(StringComparer.OrdinalIgnoreCase)); + return _parameters ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } @@ -365,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; @@ -377,7 +383,7 @@ internal static bool IsCompatibleWithConfigurationType(this DynamicKeyword keywo (ConfigurationType != ConfigurationType.Meta && !keyword.IsMetaDSCResource())); } - private static Dictionary> s_excludeKeywords = new Dictionary>(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary> s_excludeKeywords = new Dictionary>(StringComparer.OrdinalIgnoreCase) { {@"Node", new List {@"Node"}}, }; @@ -426,7 +432,7 @@ public class DynamicKeywordProperty /// public List Attributes { - get { return _attributes ?? (_attributes = new List()); } + get { return _attributes ??= new List(); } } private List _attributes; @@ -436,7 +442,7 @@ public List Attributes /// public List Values { - get { return _values ?? (_values = new List()); } + get { return _values ??= new List(); } } private List _values; @@ -446,7 +452,7 @@ public List Values /// public Dictionary ValueMap { - get { return _valueMap ?? (_valueMap = new Dictionary(StringComparer.OrdinalIgnoreCase)); } + get { return _valueMap ??= new Dictionary(StringComparer.OrdinalIgnoreCase); } } private Dictionary _valueMap; @@ -629,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[] { @@ -708,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"); @@ -721,6 +727,7 @@ internal Tokenizer(Parser parser) } internal TokenizerMode Mode { get; set; } + internal bool AllowSignedNumbers { get; set; } // TODO: use auto-properties when making 'ternary operator' an official feature. @@ -733,8 +740,11 @@ internal bool ForceEndNumberOnTernaryOpChars } internal bool WantSimpleName { get; set; } + internal bool InWorkflowContext { get; set; } + internal List TokenList { get; set; } + internal Token FirstToken { get; private set; } internal Token LastToken { get; private set; } @@ -826,7 +836,7 @@ internal void FinishNestedScan(TokenizerState ts) private char GetChar() { - Diagnostics.Assert(0 <= _currentIndex, "GetChar reading before start of input."); + Diagnostics.Assert(_currentIndex >= 0, "GetChar reading before start of input."); Diagnostics.Assert(_currentIndex <= _script.Length + 1, "GetChar reading after end of input."); // Increment _currentIndex, even if it goes over the Length so callers can call UngetChar to unget EOF. @@ -848,7 +858,7 @@ private void UngetChar() private char PeekChar() { - Diagnostics.Assert(0 <= _currentIndex && _currentIndex <= _script.Length, "PeekChar out of range."); + Diagnostics.Assert(_currentIndex >= 0 && _currentIndex <= _script.Length, "PeekChar out of range."); if (_currentIndex == _script.Length) { @@ -1045,7 +1055,7 @@ internal void Resync(int start) { _currentIndex = _script.Length + 1; } - else if (0 > _currentIndex) + else if (_currentIndex < 0) { _currentIndex = 0; } @@ -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, @@ -2056,7 +2053,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, return; } - if (!(argumentValue is string)) + if (argumentValue is not string) { ReportError(argumentAst.Extent, nameof(ParserStrings.RequiresInvalidStringArgument), @@ -2069,7 +2066,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, } else if (PSSnapinToken.StartsWith(parameter.ParameterName, StringComparison.OrdinalIgnoreCase)) { - if (!(argumentValue is string)) + if (argumentValue is not string) { ReportError(argumentAst.Extent, nameof(ParserStrings.RequiresInvalidStringArgument), @@ -2110,7 +2107,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, return; } - if (argumentValue is string || !(argumentValue is IEnumerable)) + if (argumentValue is string || argumentValue is not IEnumerable) { requiredEditions = HandleRequiresPSEditionArgument(argumentAst, argumentValue, ref requiredEditions); } @@ -2165,7 +2162,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, } else if (assemblyToken.StartsWith(parameter.ParameterName, StringComparison.OrdinalIgnoreCase)) { - if (argumentValue is string || !(argumentValue is IEnumerable)) + if (argumentValue is string || argumentValue is not IEnumerable) { requiredAssemblies = HandleRequiresAssemblyArgument(argumentAst, argumentValue, requiredAssemblies); } @@ -2204,8 +2201,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, return; } - if (requiredModules == null) - requiredModules = new List(); + requiredModules ??= new List(); requiredModules.Add(moduleSpecification); } } @@ -2219,7 +2215,7 @@ private void HandleRequiresParameter(CommandParameterAst parameter, private List HandleRequiresAssemblyArgument(Ast argumentAst, object arg, List requiredAssemblies) { - if (!(arg is string)) + if (arg is not string) { ReportError(argumentAst.Extent, nameof(ParserStrings.RequiresInvalidStringArgument), @@ -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)) { @@ -2242,7 +2237,7 @@ private List HandleRequiresAssemblyArgument(Ast argumentAst, object arg, private List HandleRequiresPSEditionArgument(Ast argumentAst, object arg, ref List requiredEditions) { - if (!(arg is string)) + if (arg is not string) { ReportError(argumentAst.Extent, nameof(ParserStrings.RequiresInvalidStringArgument), @@ -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; } @@ -2618,7 +2612,7 @@ private bool ScanAfterHereStringHeader(string header) nameof(ParserStrings.UnexpectedCharactersAfterHereStringHeader), ParserStrings.UnexpectedCharactersAfterHereStringHeader); - do + while (true) { c = GetChar(); if (c == header[1] && (PeekChar() == '@')) @@ -2632,7 +2626,7 @@ private bool ScanAfterHereStringHeader(string header) UngetChar(); break; } - } while (true); + } 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)); @@ -4243,7 +4238,7 @@ internal Token GetMemberAccessOperator(bool allowLBracket) return NewToken(TokenKind.LBracket); } - if (ExperimentalFeature.IsEnabled("PSNullConditionalOperators") && c == '?') + if (c == '?') { _tokenStart = _currentIndex; SkipChar(); @@ -4563,7 +4558,7 @@ internal string GetAssemblyNameSpec() if (PeekChar() == '=') { _tokenStart = _currentIndex; - sb.Append("="); + sb.Append('='); SkipChar(); NewToken(TokenKind.Equals); ScanAssemblyNameSpecToken(sb); diff --git a/src/System.Management.Automation/engine/pipeline.cs b/src/System.Management.Automation/engine/pipeline.cs index 8b1779de882..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; @@ -91,14 +97,6 @@ private void Dispose(bool disposing) _disposed = true; } - /// - /// Finalizer for class PipelineProcessor. - /// - ~PipelineProcessor() - { - Dispose(false); - } - #endregion IDispose #region Execution Logging @@ -123,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); @@ -174,7 +177,7 @@ internal void LogExecutionException(Exception exception) Log(message, null, PipelineExecutionStatus.Error); } - private string GetCommand(InvocationInfo invocationInfo) + private static string GetCommand(InvocationInfo invocationInfo) { if (invocationInfo == null) return string.Empty; @@ -187,7 +190,7 @@ private string GetCommand(InvocationInfo invocationInfo) return string.Empty; } - private string GetCommand(Exception exception) + private static string GetCommand(Exception exception) { IContainsErrorRecord icer = exception as IContainsErrorRecord; if (icer != null && icer.ErrorRecord != null) @@ -222,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() + private void LogToEventLog() { - if (NeedToLog()) + // 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) { - // 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); + InternalCommand firstCmd = _commands[0].Command; + MshLog.LogPipelineExecutionDetailEvent( + firstCmd.Context, + _eventLogBuffer, + firstCmd.MyInvocation); } - } - - private bool NeedToLog() - { - if (_commands == null) - return false; - - foreach (CommandProcessorBase commandProcessor in _commands) - { - MshCommandRuntime cmdRuntime = commandProcessor.Command.commandRuntime as MshCommandRuntime; - if (cmdRuntime != null && cmdRuntime.LogPipelineExecutionDetail) - return true; - } - - 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 @@ -280,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); } @@ -314,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) { @@ -344,9 +358,9 @@ internal int AddCommand(CommandProcessorBase commandProcessor, int readFromComma PipelineStrings.CommandProcessorAlreadyUsed); } - if (0 == _commands.Count) + if (_commands.Count == 0) { - if (0 != readFromCommand) + if (readFromCommand != 0) { // "First command cannot have input" throw PSTraceSource.NewArgumentException( @@ -366,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(); } @@ -400,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; @@ -420,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. @@ -486,194 +497,305 @@ 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(); + } + /// /// Implements DoComplete as a stand-alone function for completing /// the execution of a steppable pipeline. @@ -681,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; @@ -789,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 } } @@ -860,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; - } } /// @@ -943,41 +1040,29 @@ private void Start(bool incomingStream) } if (_executionStarted) + { return; + } - if (_commands == null || 0 == _commands.Count) + if (_commands == null || _commands.Count == 0) { throw PSTraceSource.NewInvalidOperationException( PipelineStrings.PipelineExecuteRequiresAtLeastOneCommand); } 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 @@ -991,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]; @@ -1016,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; @@ -1027,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; @@ -1073,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() { @@ -1083,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; } } } @@ -1101,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(); @@ -1118,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. @@ -1147,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) { @@ -1190,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(); } } @@ -1219,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; } /// @@ -1252,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; @@ -1267,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) { @@ -1296,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; + + if (e is ProviderInvocationException pie) + { + e = new CmdletProviderInvocationException(pie, myInvocation); + } + else + { + e = new CmdletInvocationException(e, myInvocation); - RecordFailure(e, commandProcessor.Command); + // Log a command health event + MshLog.LogCommandHealthEvent(commandProcessor.Command.Context, e, Severity.Warning); } -#pragma warning restore 56500 + + RecordFailure(e, commandProcessor.Command); } } } @@ -1359,39 +1413,45 @@ 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 } } _redirectionPipes = null; } - private object _stopReasonLock = new object(); + private readonly object _stopReasonLock = new object(); /// /// Makes an internal note of the exception, but only if this is /// the first error. /// /// 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; @@ -1401,13 +1461,11 @@ 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. - else if ((!(_firstTerminatingError.SourceException is PipelineStoppedException)) - && command != null && command.Context != null) + // 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) { Exception ex = e; while ((ex is TargetInvocationException || ex is CmdletInvocationException) @@ -1416,7 +1474,7 @@ internal bool RecordFailure(Exception e, InternalCommand command) ex = ex.InnerException; } - if (!(ex is PipelineStoppedException)) + if (ex is not PipelineStoppedException) { string message = StringUtil.Format(PipelineStrings.SecondFailure, _firstTerminatingError.GetType().Name, @@ -1424,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); } } @@ -1475,7 +1532,10 @@ internal void ForgetFailure() /// internal PipelineReader ExternalInput { - get { return _externalInputPipe; } + get + { + return _externalInputPipe; + } set { @@ -1501,7 +1561,10 @@ internal PipelineReader ExternalInput /// internal PipelineWriter ExternalSuccessOutput { - get { return _externalSuccessOutput; } + get + { + return _externalSuccessOutput; + } set { @@ -1528,7 +1591,10 @@ internal PipelineWriter ExternalSuccessOutput /// internal PipelineWriter ExternalErrorOutput { - get { return _externalErrorOutput; } + get + { + return _externalErrorOutput; + } set { @@ -1575,7 +1641,10 @@ internal LocalPipeline LocalPipeline /// internal SessionStateScope ExecutionScope { - get { return _executionScope; } + get + { + return _executionScope; + } set { @@ -1597,4 +1666,3 @@ internal enum PipelineExecutionStatus } } } - diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index c867f355158..6e0ef9741d6 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -43,7 +43,7 @@ public enum WildcardOptions /// Specifies culture-invariant matching. /// CultureInvariant = 4 - }; + } /// /// Represents a wildcard pattern. @@ -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; @@ -73,18 +73,6 @@ public sealed class WildcardPattern // Default is WildcardOptions.None. internal WildcardOptions Options { get; } - /// - /// Wildcard pattern converted to regex pattern. - /// - internal string PatternConvertedToRegex - { - get - { - var patternRegex = WildcardPatternToRegexParser.Parse(this); - return patternRegex.ToString(); - } - } - /// /// Initializes and instance of the WildcardPattern class /// for the specified wildcard pattern. @@ -173,8 +161,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()); @@ -184,7 +172,7 @@ StringComparison GetStringComparison() if (index == Pattern.Length - 1 && Pattern[index] == '*') { // No special characters present in the pattern before last position and last character is asterisk. - var patternWithoutAsterisk = Pattern.AsMemory().Slice(0, index); + var patternWithoutAsterisk = Pattern.AsMemory(0, index); _isMatch = str => str.AsSpan().StartsWith(patternWithoutAsterisk.Span, GetStringComparison()); return; } @@ -205,6 +193,35 @@ public bool IsMatch(string input) return input != null && _isMatch(input); } + /// + /// Converts the wildcard pattern to its regular expression equivalent. + /// + /// + /// A object that represents the regular expression equivalent of the wildcard pattern. + /// The regex is configured with options matching the wildcard pattern's options. + /// + /// + /// This method converts a wildcard pattern to a regular expression. + /// The conversion follows these rules: + /// + /// * (asterisk) converts to .* (matches any string) + /// ? (question mark) converts to . (matches any single character) + /// [abc] (bracket expression) converts to [abc] (matches any character in the set) + /// Literal characters are escaped as needed for regex + /// + /// + /// + /// + /// var pattern = new WildcardPattern("*.txt"); + /// Regex regex = pattern.ToRegex(); + /// // regex.ToString() returns: "\.txt$" + /// + /// + public Regex ToRegex() + { + return WildcardPatternToRegexParser.Parse(this); + } + /// /// Escape special chars, except for those specified in , in a string by replacing them with their escape codes. /// @@ -238,9 +255,9 @@ internal static string Escape(string pattern, char[] charsNotToEscape) char ch = pattern[i]; // - // if it is a wildcard char, escape it + // if it is a special char, escape it // - if (IsWildcardChar(ch) && !charsNotToEscape.Contains(ch)) + if (SpecialChars.Contains(ch) && !charsNotToEscape.Contains(ch)) { temp[tempIndex++] = escapeChar; } @@ -314,6 +331,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. /// @@ -325,7 +379,7 @@ public static bool ContainsWildcardCharacters(string pattern) /// converted to their unescaped form. /// /// - /// If is null. + /// If is null. /// public static string Unescape(string pattern) { @@ -432,7 +486,6 @@ public string ToWql() /// /// Thrown when a wildcard pattern is invalid. /// - [Serializable] public class WildcardPatternException : RuntimeException { /// @@ -447,16 +500,13 @@ public class WildcardPatternException : RuntimeException internal WildcardPatternException(ErrorRecord errorRecord) : base(RetrieveMessage(errorRecord)) { - if (errorRecord == null) - { - throw new ArgumentNullException(nameof(errorRecord)); - } + ArgumentNullException.ThrowIfNull(errorRecord); _errorRecord = errorRecord; } [NonSerialized] - private ErrorRecord _errorRecord; + private readonly ErrorRecord _errorRecord; /// /// Constructs an instance of the WildcardPatternException object. @@ -491,10 +541,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(); } } @@ -729,7 +780,7 @@ internal static WildcardPatternException NewWildcardPatternException(string inva return e; } - }; + } /// /// Convert a string with wild cards into its equivalent regex. @@ -1001,7 +1052,7 @@ internal bool IsMatch(string str) } } - private class PatternPositionsVisitor : IDisposable + private sealed class PatternPositionsVisitor : IDisposable { private readonly int _lengthOfPattern; @@ -1122,7 +1173,7 @@ public override void ProcessEndOfString( } } - private class LiteralCharacterElement : QuestionMarkElement + private sealed class LiteralCharacterElement : QuestionMarkElement { private readonly char _literalCharacter; @@ -1148,7 +1199,7 @@ public override void ProcessStringCharacter( } } - private class BracketExpressionElement : QuestionMarkElement + private sealed class BracketExpressionElement : QuestionMarkElement { private readonly Regex _regex; @@ -1173,7 +1224,7 @@ public override void ProcessStringCharacter( } } - private class AsterixElement : PatternElement + private sealed class AsterixElement : PatternElement { public override void ProcessStringCharacter( char currentStringCharacter, @@ -1197,7 +1248,7 @@ public override void ProcessEndOfString( } } - private class MyWildcardPatternParser : WildcardPatternParser + private sealed class MyWildcardPatternParser : WildcardPatternParser { private readonly List _patternElements = new List(); private CharacterNormalizer _characterNormalizer; @@ -1264,17 +1315,17 @@ protected override void EndBracketExpression() } } - private struct CharacterNormalizer + private readonly struct CharacterNormalizer { private readonly CultureInfo _cultureInfo; private readonly bool _caseInsensitive; public CharacterNormalizer(WildcardOptions options) { - _caseInsensitive = 0 != (options & WildcardOptions.IgnoreCase); + _caseInsensitive = (options & WildcardOptions.IgnoreCase) != 0; if (_caseInsensitive) { - _cultureInfo = 0 != (options & WildcardOptions.CultureInvariant) + _cultureInfo = (options & WildcardOptions.CultureInvariant) != 0 ? CultureInfo.InvariantCulture : CultureInfo.CurrentCulture; } @@ -1348,4 +1399,3 @@ internal static string Parse(WildcardPattern wildcardPattern) } } } - diff --git a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs index ba4e08c8f6f..9d52bcb4dc9 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs @@ -13,32 +13,32 @@ namespace System.Management.Automation.Remoting /// /// Executes methods on the client. /// - internal class ClientMethodExecutor + internal sealed class ClientMethodExecutor { /// /// Transport manager. /// - private BaseClientTransportManager _transportManager; + private readonly BaseClientTransportManager _transportManager; /// /// Client host. /// - private PSHost _clientHost; + private readonly PSHost _clientHost; /// /// Client runspace pool id. /// - private Guid _clientRunspacePoolId; + private readonly Guid _clientRunspacePoolId; /// /// Client power shell id. /// - private Guid _clientPowerShellId; + private readonly Guid _clientPowerShellId; /// /// Remote host call. /// - private RemoteHostCall _remoteHostCall; + private readonly RemoteHostCall _remoteHostCall; /// /// Remote host call. @@ -98,8 +98,7 @@ internal static void Dispatch( if (hostPrivateData != null) { PSNoteProperty allowSetShouldExit = hostPrivateData.Properties["AllowSetShouldExitFromRemote"] as PSNoteProperty; - hostAllowSetShouldExit = (allowSetShouldExit != null && allowSetShouldExit.Value is bool) ? - (bool)allowSetShouldExit.Value : false; + hostAllowSetShouldExit = allowSetShouldExit != null && allowSetShouldExit.Value is bool && (bool)allowSetShouldExit.Value; } } @@ -132,10 +131,12 @@ internal static void Dispatch( /// /// Is runspace pushed. /// - private bool IsRunspacePushed(PSHost host) + private static bool IsRunspacePushed(PSHost host) { - IHostSupportsInteractiveSession host2 = host as IHostSupportsInteractiveSession; - if (host2 == null) { return false; } + if (host is not IHostSupportsInteractiveSession host2) + { + return false; + } // IsRunspacePushed can throw (not implemented exception) try @@ -157,14 +158,11 @@ internal void Execute(PSDataCollectionStream errorStream) // If error-stream is null or we are in pushed-runspace - then write error directly to console. if (errorStream == null || IsRunspacePushed(_clientHost)) { - writeErrorAction = delegate (ErrorRecord errorRecord) + writeErrorAction = (ErrorRecord errorRecord) => { try { - if (_clientHost.UI != null) - { - _clientHost.UI.WriteErrorLine(errorRecord.ToString()); - } + _clientHost.UI?.WriteErrorLine(errorRecord.ToString()); } catch (Exception) { @@ -176,10 +174,7 @@ internal void Execute(PSDataCollectionStream errorStream) // Otherwise write it to error-stream. else { - writeErrorAction = delegate (ErrorRecord errorRecord) - { - errorStream.Write(errorRecord); - }; + writeErrorAction = (ErrorRecord errorRecord) => errorStream.Write(errorRecord); } this.Execute(writeErrorAction); diff --git a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs index 4f966f5bdba..631e49fc189 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs @@ -15,12 +15,12 @@ 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")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("CRPS", "ClientRemotePowerShellBase"); + [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(); } /// @@ -645,7 +642,7 @@ private void HandleCloseCompleted(object sender, EventArgs args) // If RemoteSessionStateEventArgs are provided then use them to set the // session close reason when setting finished state. RemoteSessionStateEventArgs sessionEventArgs = args as RemoteSessionStateEventArgs; - Exception closeReason = (sessionEventArgs != null) ? sessionEventArgs.SessionStateInfo.Reason : null; + Exception closeReason = sessionEventArgs?.SessionStateInfo.Reason; PSInvocationState finishedState = (shell.InvocationStateInfo.State == PSInvocationState.Disconnected) ? PSInvocationState.Failed : PSInvocationState.Stopped; @@ -663,7 +660,7 @@ private void HandleCloseCompleted(object sender, EventArgs args) } } - private bool IsFinished(PSInvocationState state) + private static bool IsFinished(PSInvocationState state) { return (state == PSInvocationState.Completed || state == PSInvocationState.Failed || @@ -902,64 +899,51 @@ 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 /// close is completed from the datastructure/transport layer. /// - private Queue _stateInfoQueue = new Queue(); + private readonly Queue _stateInfoQueue = new Queue(); 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 } @@ -977,7 +961,7 @@ internal enum PSConnectionRetryStatus AutoDisconnectStarting = 4, AutoDisconnectSucceeded = 5, InternalErrorAbort = 6 - }; + } /// /// PSConnectionRetryStatusEventArgs. diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index e8453457f47..adf47d04cab 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 @@ -214,7 +214,7 @@ public JobState CurrentState /// State of job when exception was thrown. /// [NonSerialized] - private JobState _currState = 0; + private readonly JobState _currState = 0; } /// @@ -371,7 +371,7 @@ internal JobIdentifier(int id, Guid instanceId) InstanceId = instanceId; } - internal int Id { get; private set; } + internal int Id { get; } internal Guid InstanceId { get; private set; } } @@ -379,12 +379,13 @@ internal JobIdentifier(int id, Guid instanceId) /// /// Interface to expose a job debugger. /// +#nullable enable public interface IJobDebugger { /// /// Job Debugger. /// - Debugger Debugger + Debugger? Debugger { get; } @@ -398,6 +399,7 @@ bool IsAsync set; } } +#nullable restore /// /// Represents a command running in background. A job object can internally @@ -631,10 +633,7 @@ public IList ChildJobs { lock (syncObject) { - if (_childJobs == null) - { - _childJobs = new List(); - } + _childJobs ??= new List(); } } @@ -642,7 +641,7 @@ public IList ChildJobs } } - /// + /// /// Success status of the command execution. /// public abstract string StatusMessage { get; } @@ -668,7 +667,10 @@ public IList ChildJobs /// public string PSJobTypeName { - get { return _jobTypeName; } + get + { + return _jobTypeName; + } protected internal set { @@ -750,12 +752,10 @@ private void WriteError(Cmdlet cmdlet, ErrorRecord errorRecord) private static Exception GetExceptionFromErrorRecord(ErrorRecord errorRecord) { - RuntimeException runtimeException = errorRecord.Exception as RuntimeException; - if (runtimeException == null) + if (errorRecord.Exception is not RuntimeException runtimeException) return null; - RemoteException remoteException = runtimeException as RemoteException; - if (remoteException == null) + if (runtimeException is not RemoteException remoteException) return null; PSPropertyInfo wasThrownFromThrow = @@ -787,12 +787,12 @@ internal void WriteError(ErrorRecord errorRecord, out Exception exceptionThrownO { this.Error.Add(errorRecord); this.InvokeCmdletMethodAndWaitForResults( - delegate (Cmdlet cmdlet) - { - this.WriteError(cmdlet, errorRecord); - return null; - }, - out exceptionThrownOnCmdletThread); + (Cmdlet cmdlet) => + { + this.WriteError(cmdlet, errorRecord); + return null; + }, + out exceptionThrownOnCmdletThread); } internal virtual void WriteWarning(string message) @@ -858,15 +858,15 @@ internal virtual void NonblockingShouldProcess( string caption) { InvokeCmdletMethodAndIgnoreResults( - delegate (Cmdlet cmdlet) - { - ShouldProcessReason throwAwayProcessReason; - cmdlet.ShouldProcess( - verboseDescription, - verboseWarning, - caption, - out throwAwayProcessReason); - }); + (Cmdlet cmdlet) => + { + ShouldProcessReason throwAwayProcessReason; + cmdlet.ShouldProcess( + verboseDescription, + verboseWarning, + caption, + out throwAwayProcessReason); + }); } internal virtual bool ShouldProcess( @@ -895,7 +895,7 @@ private void InvokeCmdletMethodAndIgnoreResults(Action invokeCmdletMetho object resultsLock = new object(); CmdletMethodInvoker methodInvoker = new CmdletMethodInvoker { - Action = delegate (Cmdlet cmdlet) { invokeCmdletMethod(cmdlet); return null; }, + Action = (Cmdlet cmdlet) => { invokeCmdletMethod(cmdlet); return null; }, Finished = null, SyncObject = resultsLock }; @@ -912,18 +912,18 @@ private T InvokeCmdletMethodAndWaitForResults(Func invokeCmdletMet using (var gotResultEvent = new ManualResetEventSlim(false)) { EventHandler stateChangedEventHandler = - delegate (object sender, JobStateEventArgs eventArgs) + (object sender, JobStateEventArgs eventArgs) => + { + if (IsFinishedState(eventArgs.JobStateInfo.State) || eventArgs.JobStateInfo.State == JobState.Stopping) { - if (IsFinishedState(eventArgs.JobStateInfo.State) || eventArgs.JobStateInfo.State == JobState.Stopping) + lock (resultsLock) { - lock (resultsLock) - { - closureSafeExceptionThrownOnCmdletThread = new OperationCanceledException(); - } - - gotResultEvent.Set(); + closureSafeExceptionThrownOnCmdletThread = new OperationCanceledException(); } - }; + + gotResultEvent.Set(); + } + }; this.StateChanged += stateChangedEventHandler; Interlocked.MemoryBarrier(); try @@ -1011,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; } @@ -1448,10 +1454,7 @@ internal void SetJobState(JobState state, Exception reason) { lock (syncObject) { - if (_finished != null) - { - _finished.Set(); - } + _finished?.Set(); } } #pragma warning restore 56500 @@ -1742,8 +1745,7 @@ internal class PSRemotingJob : Job [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal PSRemotingJob(string[] computerNames, List computerNameHelpers, string remoteCommand, string name) - : - this(computerNames, computerNameHelpers, remoteCommand, 0, name) + : this(computerNames, computerNameHelpers, remoteCommand, 0, name) { } /// @@ -1760,8 +1762,7 @@ internal PSRemotingJob(string[] computerNames, /// internal PSRemotingJob(PSSession[] remoteRunspaceInfos, List runspaceHelpers, string remoteCommand, string name) - : - this(remoteRunspaceInfos, runspaceHelpers, remoteCommand, 0, name) + : this(remoteRunspaceInfos, runspaceHelpers, remoteCommand, 0, name) { } /// @@ -1910,8 +1911,7 @@ internal List GetJobsForComputer(string computerName) foreach (Job j in ChildJobs) { - PSRemotingChildJob child = j as PSRemotingChildJob; - if (child == null) continue; + if (j is not PSRemotingChildJob child) continue; if (string.Equals(child.Runspace.ConnectionInfo.ComputerName, computerName, StringComparison.OrdinalIgnoreCase)) { @@ -1934,8 +1934,7 @@ internal List GetJobsForRunspace(PSSession runspace) foreach (Job j in ChildJobs) { - PSRemotingChildJob child = j as PSRemotingChildJob; - if (child == null) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Runspace.InstanceId.Equals(runspace.InstanceId)) { returnJobList.Add(child); @@ -1958,8 +1957,7 @@ internal List GetJobsForOperation(IThrottleOperation operation) foreach (Job j in ChildJobs) { - PSRemotingChildJob child = j as PSRemotingChildJob; - if (child == null) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Helper.Equals(helper)) { returnJobList.Add(child); @@ -2021,17 +2019,14 @@ internal void ConnectJob(Guid runspaceInstanceId) SubmitAndWaitForConnect(connectJobOperations); } - private void SubmitAndWaitForConnect(List connectJobOperations) + private static void SubmitAndWaitForConnect(List connectJobOperations) { using (ThrottleManager connectThrottleManager = new ThrottleManager()) { using (ManualResetEvent connectResult = new ManualResetEvent(false)) { EventHandler throttleCompleteEventHandler = - delegate (object sender, EventArgs eventArgs) - { - connectResult.Set(); - }; + (object sender, EventArgs eventArgs) => connectResult.Set(); connectThrottleManager.ThrottleComplete += throttleCompleteEventHandler; try @@ -2053,9 +2048,9 @@ private void SubmitAndWaitForConnect(List connectJobOperatio /// /// Simple throttle operation class for connecting jobs. /// - private class ConnectJobOperation : IThrottleOperation + private sealed class ConnectJobOperation : IThrottleOperation { - private PSRemotingChildJob _psRemoteChildJob; + private readonly PSRemotingChildJob _psRemoteChildJob; internal ConnectJobOperation(PSRemotingChildJob job) { @@ -2294,7 +2289,10 @@ public override string StatusMessage /// internal bool HideComputerName { - get { return _hideComputerName; } + get + { + return _hideComputerName; + } set { @@ -2316,7 +2314,6 @@ internal bool HideComputerName /// /// Checks the status of remote command execution. /// - [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] private void SetStatusMessage() { @@ -2363,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 @@ -2586,7 +2583,7 @@ private string ConstructLocation() foreach (PSRemotingChildJob job in ChildJobs) { location.Append(job.Location); - location.Append(","); + location.Append(','); } location.Remove(location.Length - 1, 1); @@ -2667,7 +2664,7 @@ private void HandleJobUnblocked(object sender, EventArgs eventArgs) #region Private Members - private ThrottleManager _throttleManager = new ThrottleManager(); + private readonly ThrottleManager _throttleManager = new ThrottleManager(); private readonly object _syncObject = new object(); // sync object @@ -2943,7 +2940,10 @@ public override string Location /// internal bool HideComputerName { - get { return _hideComputerName; } + get + { + return _hideComputerName; + } set { @@ -2977,7 +2977,7 @@ internal override bool CanDisconnect get { RemoteRunspace remoteRS = Runspace as RemoteRunspace; - return (remoteRS != null) ? remoteRS.CanDisconnect : false; + return remoteRS != null && remoteRS.CanDisconnect; } } @@ -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,21 +3370,19 @@ 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, null, null, null, null, null, errorDetails, null); } - else if ((pipeline.PipelineStateInfo.State == PipelineState.Failed) || - ((pipeline.PipelineStateInfo.State == PipelineState.Stopped) && - (pipeline.PipelineStateInfo.Reason != null && !(pipeline.PipelineStateInfo.Reason is PipelineStoppedException)))) + else if (pipeline.PipelineStateInfo.State == PipelineState.Failed + || (pipeline.PipelineStateInfo.State == PipelineState.Stopped + && pipeline.PipelineStateInfo.Reason != null + && pipeline.PipelineStateInfo.Reason is not PipelineStoppedException)) { // Pipeline stopped state is also an error condition if the associated exception is not 'PipelineStoppedException'. object targetObject = runspace.ConnectionInfo.ComputerName; @@ -3477,11 +3474,11 @@ protected override void Dispose(bool disposing) protected virtual void DoCleanupOnFinished() { bool doCleanup = false; - if (_cleanupDone == false) + if (!_cleanupDone) { lock (SyncObject) { - if (_cleanupDone == false) + if (!_cleanupDone) { _cleanupDone = true; doCleanup = true; @@ -3825,7 +3822,7 @@ private void HandleRunspaceAvailabilityChanged(object sender, RunspaceAvailabili #region Private Members // helper associated with this job object - private RemotePipeline _remotePipeline = null; + private readonly RemotePipeline _remotePipeline = null; // object used for synchronization protected object SyncObject = new object(); @@ -3850,9 +3847,9 @@ internal sealed class RemotingJobDebugger : Debugger { #region Members - private Debugger _wrappedDebugger; - private Runspace _runspace; - private string _jobName; + private readonly Debugger _wrappedDebugger; + private readonly Runspace _runspace; + private readonly string _jobName; #endregion @@ -3925,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); @@ -4079,10 +4076,7 @@ public override void SetDebuggerStepMode(bool enabled) internal void CheckStateAndRaiseStopEvent() { RemoteDebugger remoteDebugger = _wrappedDebugger as RemoteDebugger; - if (remoteDebugger != null) - { - remoteDebugger.CheckStateAndRaiseStopEvent(); - } + remoteDebugger?.CheckStateAndRaiseStopEvent(); } /// @@ -4116,7 +4110,7 @@ private void HandleDebuggerStop(object sender, DebuggerStopEventArgs e) private Pipeline DrainAndBlockRemoteOutput() { // We only do this for remote runspaces. - if (!(_runspace is RemoteRunspace)) { return null; } + if (_runspace is not RemoteRunspace) { return null; } Pipeline runningCmd = _runspace.GetCurrentlyRunningPipeline(); if (runningCmd != null) @@ -4130,13 +4124,7 @@ private Pipeline DrainAndBlockRemoteOutput() return null; } - private void RestoreRemoteOutput(Pipeline runningCmd) - { - if (runningCmd != null) - { - runningCmd.ResumeIncomingData(); - } - } + private static void RestoreRemoteOutput(Pipeline runningCmd) => runningCmd?.ResumeIncomingData(); private void HandleBreakpointUpdated(object sender, BreakpointUpdatedEventArgs e) { @@ -4172,9 +4160,9 @@ internal class PSInvokeExpressionSyncJob : PSRemotingChildJob { #region Private Members - private List _helpers = new List(); - private ThrottleManager _throttleManager; - private Dictionary _powershells = new Dictionary(); + private readonly List _helpers = new List(); + private readonly ThrottleManager _throttleManager; + private readonly Dictionary _powershells = new Dictionary(); private int _pipelineFinishedCount; private int _pipelineDisconnectedCount; @@ -4233,11 +4221,11 @@ internal PSInvokeExpressionSyncJob(List operations, Throttle protected override void DoCleanupOnFinished() { bool doCleanup = false; - if (_cleanupDone == false) + if (!_cleanupDone) { lock (SyncObject) { - if (_cleanupDone == false) + if (!_cleanupDone) { _cleanupDone = true; doCleanup = true; @@ -4518,11 +4506,7 @@ internal PSRemotingJob CreateDisconnectedRemotingJob() internal class OutputProcessingStateEventArgs : EventArgs { - internal bool ProcessingOutput - { - get; - private set; - } + internal bool ProcessingOutput { get; } internal OutputProcessingStateEventArgs(bool processingOutput) { @@ -4530,9 +4514,10 @@ internal OutputProcessingStateEventArgs(bool processingOutput) } } +#nullable enable internal interface IOutputProcessingState { - event EventHandler OutputProcessingStateChanged; + event EventHandler? OutputProcessingStateChanged; } #endregion diff --git a/src/System.Management.Automation/engine/remoting/client/Job2.cs b/src/System.Management.Automation/engine/remoting/client/Job2.cs index 32923806cce..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(); } } @@ -342,10 +341,7 @@ private void RaiseCompletedHandler(int operation, AsyncCompletedEventArgs eventA #pragma warning disable 56500 try { - if (handler != null) - { - handler(this, eventArgs); - } + handler?.Invoke(this, eventArgs); } catch (Exception exception) { @@ -509,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; @@ -536,7 +532,10 @@ public sealed class ContainerParentJob : Job2 internal PSEventManager EventManager { - get { return _eventManager; } + get + { + return _eventManager; + } set { @@ -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()); @@ -888,7 +885,7 @@ public override void StartJob() _tracer.WriteMessage(TraceClassName, "StartJob", Guid.Empty, this, "Exiting method", null); } - private static Tracer s_structuredTracer = new Tracer(); + private static readonly Tracer s_structuredTracer = new Tracer(); /// /// Starts all child jobs asynchronously. @@ -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; } @@ -2058,7 +2052,7 @@ private string ConstructStatusMessage() if (i < (ChildJobs.Count - 1)) { - sb.Append(","); + sb.Append(','); } } @@ -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(); } /// @@ -2169,30 +2161,14 @@ protected JobFailedException(SerializationInfo serializationInfo, StreamingConte /// public Exception Reason { get { return _reason; } } - private Exception _reason; + private readonly Exception _reason; /// /// The user-focused location from where this error originated. /// public ScriptExtent DisplayScriptPosition { get { return _displayScriptPosition; } } - private 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); - } + private readonly ScriptExtent _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 6ef47c19752..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); } } @@ -616,7 +618,7 @@ private List GetFilteredJobs( /// /// /// - private bool CheckTypeNames(JobSourceAdapter sourceAdapter, string[] jobSourceAdapterTypes) + private static bool CheckTypeNames(JobSourceAdapter sourceAdapter, string[] jobSourceAdapterTypes) { // If no type names were specified then allow all adapter types. if (jobSourceAdapterTypes == null || @@ -641,9 +643,9 @@ private bool CheckTypeNames(JobSourceAdapter sourceAdapter, string[] jobSourceAd return false; } - private string GetAdapterName(JobSourceAdapter sourceAdapter) + private static string GetAdapterName(JobSourceAdapter sourceAdapter) { - return (string.IsNullOrEmpty(sourceAdapter.Name) == false ? + return (!string.IsNullOrEmpty(sourceAdapter.Name) ? sourceAdapter.Name : sourceAdapter.GetType().ToString()); } @@ -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 f38ac4a39d0..85cd6c7ba1f 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 { /// @@ -238,7 +236,7 @@ public JobDefinition Definition /// public List Parameters { - get { return _parameters ?? (_parameters = new List()); } + get { return _parameters ??= new List(); } } /// @@ -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); } @@ -415,8 +413,7 @@ public void StoreJobIdForReuse(Job2 job, bool recurse) duplicateDetector.Add(job.InstanceId, job.InstanceId); foreach (Job child in job.ChildJobs) { - Job2 childJob = child as Job2; - if (childJob == null) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, true); } } @@ -432,8 +429,7 @@ private void StoreJobIdForReuseHelper(Hashtable duplicateDetector, Job2 job, boo if (!recurse || job.ChildJobs == null) return; foreach (Job child in job.ChildJobs) { - Job2 childJob = child as Job2; - if (childJob == null) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, recurse); } } diff --git a/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs b/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs index 288bc1449e9..df859a7bc2b 100644 --- a/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs +++ b/src/System.Management.Automation/engine/remoting/client/PowerShellStreams.cs @@ -238,11 +238,11 @@ public PSDataCollection InformationStream /// public void CloseAll() { - if (_disposed == false) + if (!_disposed) { lock (_syncLock) { - if (_disposed == false) + if (!_disposed) { _outputStream.Complete(); _errorStream.Complete(); diff --git a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs index 7c2aceec7d4..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -23,7 +23,7 @@ namespace System.Management.Automation.Runspaces.Internal /// Class which supports pooling remote powerShell runspaces /// on the client. /// - 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; @@ -111,9 +111,9 @@ internal RemoteRunspacePoolInternal(Guid instanceId, string name, bool isDisconn ConnectCommandInfo[] connectCommands, RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) : base(1, 1) { - if (instanceId == null) + if (instanceId == Guid.Empty) { - throw PSTraceSource.NewArgumentNullException("RunspacePool Guid"); + throw PSTraceSource.NewArgumentException(nameof(instanceId)); } if (connectCommands == null) @@ -128,7 +128,7 @@ internal RemoteRunspacePoolInternal(Guid instanceId, string name, bool isDisconn if (connectionInfo is WSManConnectionInfo) { - _connectionInfo = connectionInfo.InternalCopy(); + _connectionInfo = connectionInfo.Clone(); } else { @@ -271,8 +271,8 @@ public override RunspacePoolAvailability RunspacePoolAvailability /// internal bool IsRemoteDebugStop { - set; get; + set; } #endregion @@ -291,7 +291,7 @@ internal override bool ResetRunspaceState() // version 2.3 or greater. Version remoteProtocolVersionDeclaredByServer = PSRemotingProtocolVersion; if ((remoteProtocolVersionDeclaredByServer == null) || - (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersionWin10RTM)) + (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersion_2_3)) { throw PSTraceSource.NewInvalidOperationException(RunspacePoolStrings.ResetRunspaceStateNotSupportedOnServer); } @@ -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 @@ -679,7 +679,7 @@ internal override void PropagateApplicationPrivateData(Runspace runspace) } private PSPrimitiveDictionary _applicationPrivateData; - private ManualResetEvent _applicationPrivateDataReceived = new ManualResetEvent(false); + private readonly ManualResetEvent _applicationPrivateDataReceived = new ManualResetEvent(false); /// /// This event is raised, when a host call is for a remote runspace @@ -733,7 +733,7 @@ internal bool CanDisconnect { // Disconnect/Connect support is currently only provided by the WSMan transport // that is running PSRP protocol version 2.2 and greater. - return (remoteProtocolVersionDeclaredByServer >= RemotingConstants.ProtocolVersionWin8RTM && + return (remoteProtocolVersionDeclaredByServer >= RemotingConstants.ProtocolVersion_2_2 && DataStructureHandler.EndpointSupportsDisconnect); } @@ -1215,7 +1215,7 @@ public override Collection CreateDisconnectedPowerShells(RunspacePoo return psCollection; } - /// + /// /// Returns RunspacePool capabilities. /// /// RunspacePoolCapability. @@ -1237,11 +1237,9 @@ public override RunspacePoolCapability GetCapabilities() internal static RunspacePool[] GetRemoteRunspacePools(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { - WSManConnectionInfo wsmanConnectionInfoParam = connectionInfo as WSManConnectionInfo; - - // Disconnect-Connect currently only supported by WSMan. - if (wsmanConnectionInfoParam == null) + if (connectionInfo is not WSManConnectionInfo wsmanConnectionInfoParam) { + // Disconnect-Connect currently only supported by WSMan. throw new NotSupportedException(); } @@ -1270,7 +1268,7 @@ internal static RunspacePool[] GetRemoteRunspacePools(RunspaceConnectionInfo con Guid shellId = Guid.Parse(pspShellId.Value.ToString()); // Filter returned items for PowerShell sessions. - if (strShellUri.StartsWith(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) == false) + if (!strShellUri.StartsWith(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase)) { continue; } @@ -1428,8 +1426,7 @@ private static void UpdateWSManConnectionInfo( string compressionModeString = pspCompressionMode.Value as string; if (compressionModeString != null) { - wsmanConnectionInfo.UseCompression = compressionModeString.Equals("NoCompression", StringComparison.OrdinalIgnoreCase) - ? false : true; + wsmanConnectionInfo.UseCompression = !compressionModeString.Equals("NoCompression", StringComparison.OrdinalIgnoreCase); } } @@ -1438,8 +1435,7 @@ private static void UpdateWSManConnectionInfo( string encodingString = pspEncoding.Value as string; if (encodingString != null) { - wsmanConnectionInfo.UseUTF16 = encodingString.Equals("UTF16", StringComparison.OrdinalIgnoreCase) - ? true : false; + wsmanConnectionInfo.UseUTF16 = encodingString.Equals("UTF16", StringComparison.OrdinalIgnoreCase); } } @@ -1448,8 +1444,7 @@ private static void UpdateWSManConnectionInfo( string machineProfileLoadedString = pspProfile.Value as string; if (machineProfileLoadedString != null) { - wsmanConnectionInfo.NoMachineProfile = machineProfileLoadedString.Equals("Yes", StringComparison.OrdinalIgnoreCase) - ? false : true; + wsmanConnectionInfo.NoMachineProfile = !machineProfileLoadedString.Equals("Yes", StringComparison.OrdinalIgnoreCase); } } @@ -1831,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(); } /// @@ -1881,10 +1870,10 @@ private void WaitAndRaiseConnectEventsProc(object state) #region Private Members - private RunspaceConnectionInfo _connectionInfo; // connection info with which this + private readonly RunspaceConnectionInfo _connectionInfo; // connection info with which this // runspace is created // data structure handler handling - private RunspacePoolAsyncResult _openAsyncResult;// async result object generated on + private RunspacePoolAsyncResult _openAsyncResult; // async result object generated on // CoreOpen private RunspacePoolAsyncResult _closeAsyncResult; // async result object generated by // BeginClose @@ -1898,27 +1887,17 @@ private void WaitAndRaiseConnectEventsProc(object state) private bool _canReconnect; private string _friendlyName = string.Empty; - private System.Collections.Concurrent.ConcurrentStack _runningPowerShells; + private readonly System.Collections.Concurrent.ConcurrentStack _runningPowerShells; #endregion Private Members #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); @@ -2044,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 7a7a70f9296..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 { /// @@ -25,14 +24,15 @@ public OriginInfo OriginInfo } } - private OriginInfo _originInfo; + private readonly OriginInfo _originInfo; /// /// Constructor. /// /// The error record that is wrapped. /// Origin information. - public RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo) : this(errorRecord, originInfo, null) { } + public RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo) + : this(errorRecord, originInfo, null) { } /// /// Constructor that is used to wrap an error record. @@ -40,8 +40,11 @@ public RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo) : thi /// /// /// - private RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo, Exception replaceParentContainsErrorRecordException) : - base(errorRecord, replaceParentContainsErrorRecordException) + private RemotingErrorRecord( + ErrorRecord errorRecord, + OriginInfo originInfo, + Exception replaceParentContainsErrorRecordException) + : base(errorRecord, replaceParentContainsErrorRecordException) { if (errorRecord != null) { @@ -53,33 +56,15 @@ private RemotingErrorRecord(ErrorRecord errorRecord, OriginInfo originInfo, Exce #region ISerializable implementation - /// - /// Serializer method for class. - /// - /// Serializer information. - /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -105,7 +90,7 @@ internal override ErrorRecord WrapException(Exception replaceParentContainsError /// /// Progress record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingProgressRecord : ProgressRecord { /// @@ -116,7 +101,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -124,8 +109,11 @@ public OriginInfo OriginInfo /// /// The progress record that is wrapped. /// Origin information. - public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originInfo) : - base(Validate(progressRecord).ActivityId, Validate(progressRecord).Activity, Validate(progressRecord).StatusDescription) + public RemotingProgressRecord(ProgressRecord progressRecord, OriginInfo originInfo) + : base( + Validate(progressRecord).ActivityId, + Validate(progressRecord).Activity, + Validate(progressRecord).StatusDescription) { _originInfo = originInfo; if (progressRecord != null) @@ -143,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; } } @@ -151,7 +139,7 @@ private static ProgressRecord Validate(ProgressRecord progressRecord) /// /// Warning record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingWarningRecord : WarningRecord { /// @@ -162,7 +150,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -170,7 +158,8 @@ public OriginInfo OriginInfo /// /// The warning message that is wrapped. /// The origin information. - public RemotingWarningRecord(string message, OriginInfo originInfo) : base(message) + public RemotingWarningRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } @@ -192,7 +181,7 @@ internal RemotingWarningRecord( /// /// Debug record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingDebugRecord : DebugRecord { /// @@ -203,7 +192,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -211,7 +200,8 @@ public OriginInfo OriginInfo /// /// The debug message that is wrapped. /// The origin information. - public RemotingDebugRecord(string message, OriginInfo originInfo) : base(message) + public RemotingDebugRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } @@ -220,7 +210,7 @@ public RemotingDebugRecord(string message, OriginInfo originInfo) : base(message /// /// Verbose record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingVerboseRecord : VerboseRecord { /// @@ -231,7 +221,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -239,7 +229,8 @@ public OriginInfo OriginInfo /// /// The verbose message that is wrapped. /// The origin information. - public RemotingVerboseRecord(string message, OriginInfo originInfo) : base(message) + public RemotingVerboseRecord(string message, OriginInfo originInfo) + : base(message) { _originInfo = originInfo; } @@ -248,7 +239,7 @@ public RemotingVerboseRecord(string message, OriginInfo originInfo) : base(messa /// /// Information record containing origin information. /// - [DataContract()] + [DataContract] public class RemotingInformationRecord : InformationRecord { /// @@ -259,7 +250,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute()] + [DataMember] private readonly OriginInfo _originInfo; /// @@ -285,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 { /// @@ -302,8 +292,8 @@ public string PSComputerName } } - [DataMemberAttribute()] - private string _computerName; + [DataMember] + private readonly string _computerName; /// /// Runspace instance ID. @@ -317,8 +307,8 @@ public Guid RunspaceID } } - [DataMemberAttribute()] - private Guid _runspaceID; + [DataMember] + private readonly Guid _runspaceID; /// /// Error record source instance ID. @@ -337,7 +327,7 @@ public Guid InstanceID } } - [DataMemberAttribute()] + [DataMember] private Guid _instanceId; /// @@ -374,4 +364,3 @@ public override string ToString() } } } - diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index 11ce70dd186..441a3f62fed 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs @@ -19,7 +19,7 @@ namespace System.Management.Automation.Internal /// Handles all PowerShell data structure handler communication with the /// server side RunspacePool. /// - internal class ClientRunspacePoolDataStructureHandler : IDisposable + internal sealed class ClientRunspacePoolDataStructureHandler : IDisposable { private bool _reconnecting = false; @@ -233,7 +233,7 @@ internal void CreatePowerShellOnServerAndInvoke(ClientRemotePowerShell shell) // Find out if this is an invoke and disconnect operation and if so whether the endpoint // supports disconnect. Throw exception if disconnect is not supported. - bool invokeAndDisconnect = (shell.Settings != null) ? shell.Settings.InvokeAndDisconnect : false; + bool invokeAndDisconnect = shell.Settings != null && shell.Settings.InvokeAndDisconnect; if (invokeAndDisconnect && !EndpointSupportsDisconnect) { throw new PSRemotingDataStructureException(RemotingErrorIdStrings.EndpointDoesNotSupportDisconnect); @@ -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. + } } /// @@ -862,22 +864,23 @@ private void HandleSessionCreateCompleted(object sender, CreateCompleteEventArgs #region Private Members - private Guid _clientRunspacePoolId; - private object _syncObject = new object(); + private readonly Guid _clientRunspacePoolId; + private readonly object _syncObject = new object(); private bool _createRunspaceCalled = false; private Exception _closingReason; - private int _minRunspaces; - private int _maxRunspaces; - private PSHost _host; - private PSPrimitiveDictionary _applicationArguments; + private readonly int _minRunspaces; + private readonly int _maxRunspaces; + private readonly PSHost _host; + private readonly PSPrimitiveDictionary _applicationArguments; - private Dictionary _associatedPowerShellDSHandlers + private readonly Dictionary _associatedPowerShellDSHandlers = new Dictionary(); + // data structure handlers of all ClientRemotePowerShell which are // associated with this runspace pool - private object _associationSyncObject = new object(); + private readonly object _associationSyncObject = new object(); // object to synchronize operations to above - private BaseClientSessionTransportManager _transportManager; + private readonly BaseClientSessionTransportManager _transportManager; // session transport manager associated with this runspace private List _preparingForDisconnectList; @@ -937,7 +940,7 @@ internal bool EndpointSupportsDisconnect get { WSManClientSessionTransportManager wsmanTransportManager = _transportManager as WSManClientSessionTransportManager; - return (wsmanTransportManager != null) ? wsmanTransportManager.SupportsDisconnect : false; + return wsmanTransportManager != null && wsmanTransportManager.SupportsDisconnect; } } @@ -978,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 @@ -1149,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, @@ -1172,7 +1175,7 @@ internal void SendInput(ObjectStreamBase inputstream) { // send input closed information to server SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } else @@ -1201,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 @@ -1373,7 +1376,7 @@ internal void CloseConnectionAsync(Exception sessionCloseReason) _sessionClosedReason = sessionCloseReason; // wait for the close to complete and then dispose the transport manager - TransportManager.CloseCompleted += delegate (object source, EventArgs args) + TransportManager.CloseCompleted += (object source, EventArgs args) => { if (CloseCompleted != null) { @@ -1412,7 +1415,7 @@ internal void ProcessDisconnect(RunspacePoolStateInfo rsStateInfo) // disconnect may be called on a pipeline that is already disconnected. PSInvocationStateInfo stateInfo = new PSInvocationStateInfo(PSInvocationState.Disconnected, - (rsStateInfo != null) ? rsStateInfo.Reason : null); + rsStateInfo?.Reason); Dbg.Assert(InvocationStateInfoReceived != null, "ClientRemotePowerShell should subscribe to all data structure handler events"); @@ -1424,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. /// @@ -1467,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 /// @@ -1490,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; } @@ -1507,7 +1503,7 @@ internal Guid PowerShellId { get { - return clientPowerShellId; + return _clientPowerShellId; } } @@ -1555,23 +1551,23 @@ private void HandleInputDataReady(object sender, EventArgs e) /// private void WriteInput(ObjectStreamBase inputstream) { - Collection inputObjects = inputstream.ObjectReader.NonBlockingRead(Int32.MaxValue); + Collection inputObjects = inputstream.ObjectReader.NonBlockingRead(int.MaxValue); foreach (object inputObject in inputObjects) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } if (!inputstream.IsOpen) { // Write any data written after the NonBlockingRead call above. - inputObjects = inputstream.ObjectReader.NonBlockingRead(Int32.MaxValue); + inputObjects = inputstream.ObjectReader.NonBlockingRead(int.MaxValue); foreach (object inputObject in inputObjects) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } // we are sending input end to the server. Ignore the future @@ -1580,7 +1576,7 @@ private void WriteInput(ObjectStreamBase inputstream) inputstream.DataReady -= HandleInputDataReady; // stream close: send end of input SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } @@ -1602,9 +1598,12 @@ 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 object _inputSyncObject = new object(); + private readonly object _inputSyncObject = new object(); private enum connectionStates { @@ -1620,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 270e24eb1bd..91cf4161a99 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; @@ -22,10 +23,10 @@ internal class RunspaceRef /// /// Runspace ref. /// - private ObjectRef _runspaceRef; + private readonly ObjectRef _runspaceRef; private bool _stopInvoke; - private object _localSyncObject; - private static RobustConnectionProgress s_RCProgress = new RobustConnectionProgress(); + private readonly object _localSyncObject; + private static readonly RobustConnectionProgress s_RCProgress = new RobustConnectionProgress(); /// /// Constructor for RunspaceRef. @@ -93,8 +94,23 @@ private PSCommand ParsePsCommandUsingScriptBlock(string line, bool? useLocalScop // and if we are not in a loopback configuration mode, in which case we always force remote script commands // 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 : false; - bool isTrustedInput = !isConfiguredLoopback && (localRunspace.ExecutionContext.LanguageMode == PSLanguageMode.FullLanguage); + bool isConfiguredLoopback = remoteRunspace != null && remoteRunspace.IsConfiguredLoopBack; + + 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); @@ -139,7 +155,7 @@ internal PSCommand CreatePsCommand(string line, bool isScript, bool? useNewScope /// /// Creates the PSCommand when the runspace is not overridden. /// - private PSCommand CreatePsCommandNotOverridden(string line, bool isScript, bool? useNewScope) + private static PSCommand CreatePsCommandNotOverridden(string line, bool isScript, bool? useNewScope) { PSCommand command = new PSCommand(); @@ -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; @@ -313,7 +326,7 @@ internal void Override(RemoteRunspace remoteRunspace, object syncObject, out boo powerShell.AddParameter("Name", new string[] { "Out-Default", "Exit-PSSession" }); powerShell.Runspace = _runspaceRef.Value; - bool isReleaseCandidateBackcompatibilityMode = _runspaceRef.Value.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersionWin7RC; + bool isReleaseCandidateBackcompatibilityMode = _runspaceRef.Value.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersion_2_0; powerShell.IsGetCommandMetadataSpecialPipeline = !isReleaseCandidateBackcompatibilityMode; int expectedNumberOfResults = isReleaseCandidateBackcompatibilityMode ? 2 : 3; @@ -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 @@ -407,7 +420,7 @@ private void StartProgressBar( } } - private void StopProgressBar( + private static void StopProgressBar( long sourceId) { s_RCProgress.StopProgress(sourceId); diff --git a/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs b/src/System.Management.Automation/engine/remoting/client/ThrottlingJob.cs index efb47508bcc..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(); } } @@ -191,8 +187,8 @@ internal enum ChildJobFlags /// /// Child job can call method - /// or - /// or + /// or + /// or /// method /// of the instance it belongs to. /// @@ -269,12 +265,12 @@ private int CountOfRunningOrReadyToRunChildJobs /// Passing 0 requests to turn off throttling (i.e. allow unlimited number of child jobs to run) /// /// - /// true if this is used from a cmdlet invoked without -AsJob switch. - /// false if this is used from a cmdlet invoked with -AsJob switch. + /// if this is used from a cmdlet invoked without -AsJob switch. + /// if this is used from a cmdlet invoked with -AsJob switch. /// - /// If is true, then + /// If is , then /// memory can be managed more aggressively (for example ChildJobs can be discarded as soon as they complete) - /// because the is not exposed to the end user. + /// because the is not exposed to the end user. /// internal ThrottlingJob(string command, string jobName, string jobTypeName, int maximumConcurrentChildJobs, bool cmdletMode) : base(command, jobName) @@ -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); @@ -337,10 +333,7 @@ internal void DisableFlowControlForPendingJobsQueue() while (_actionsForUnblockingChildAdditions.Count > 0) { Action a = _actionsForUnblockingChildAdditions.Dequeue(); - if (a != null) - { - a(); - } + a?.Invoke(); } } } @@ -375,22 +368,33 @@ 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) { newJobStateInfo = new JobStateInfo(JobState.Running); } - if (ChildJobFlags.CreatesChildJobs == (ChildJobFlags.CreatesChildJobs & flags)) + if ((ChildJobFlags.CreatesChildJobs & flags) == ChildJobFlags.CreatesChildJobs) { _setOfChildJobsThatCanAddMoreChildJobs.Add(childJob.InstanceId); } @@ -406,10 +410,7 @@ internal void AddChildJobWithoutBlocking(StartableJob childJob, ChildJobFlags fl } else { - if (jobEnqueuedAction != null) - { - jobEnqueuedAction(); - } + jobEnqueuedAction?.Invoke(); } } @@ -547,10 +548,7 @@ private void StartChildJobIfPossible() } while (false); } - if (readyToRunChildJob != null) - { - readyToRunChildJob.StartJob(); - } + readyToRunChildJob?.StartJob(); } private void EnqueueReadyToRunChildJob(StartableJob childJob) @@ -746,10 +744,7 @@ private void childJob_StateChanged(object sender, JobStateEventArgs e) if (_actionsForUnblockingChildAdditions.Count > 0) { Action a = _actionsForUnblockingChildAdditions.Dequeue(); - if (a != null) - { - a(); - } + a?.Invoke(); } if (_cmdletMode) @@ -786,14 +781,14 @@ private List GetChildJobsSnapshot() /// /// Indicates if job has more data available. - /// true if any of the child jobs have more data OR if have not been called yet; - /// false otherwise. + /// if any of the child jobs have more data OR if have not been called yet; + /// otherwise. /// 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); } } @@ -855,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 @@ -1236,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 a2875f819fe..499062d7a18 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs @@ -55,8 +55,8 @@ internal class ClientRemoteSessionContext /// internal abstract class ClientRemoteSession : RemoteSession { - [TraceSourceAttribute("CRSession", "ClientRemoteSession")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("CRSession", "ClientRemoteSession"); + [TraceSource("CRSession", "ClientRemoteSession")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSession", "ClientRemoteSession"); #region Public_Method_API @@ -177,8 +177,8 @@ internal RemoteRunspacePoolInternal GetRunspacePool(Guid clientRunspacePoolId) /// internal class ClientRemoteSessionImpl : ClientRemoteSession, IDisposable { - [TraceSourceAttribute("CRSessionImpl", "ClientRemoteSessionImpl")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionImpl", "ClientRemoteSessionImpl"); + [TraceSource("CRSessionImpl", "ClientRemoteSessionImpl")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionImpl", "ClientRemoteSessionImpl"); private PSRemotingCryptoHelperClient _cryptoHelper = null; @@ -505,20 +505,11 @@ private bool RunClientNegotiationAlgorithm(RemoteSessionCapability serverRemoteS _serverProtocolVersion = serverProtocolVersion; Version clientProtocolVersion = Context.ClientCapability.ProtocolVersion; - if ( - clientProtocolVersion.Equals(serverProtocolVersion) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM && - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM - )) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM && - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM - )) - ) + if (clientProtocolVersion == serverProtocolVersion || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_0 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_1 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { // passed negotiation check } @@ -604,4 +595,3 @@ public void Dispose(bool disposing) #endregion IDisposable } } - diff --git a/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs index ec55f22fc22..2dea06d8ced 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs @@ -31,26 +31,27 @@ namespace System.Management.Automation.Remoting /// internal class ClientRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("CRSessionFSM", "CRSessionFSM")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionFSM", "CRSessionFSM"); + [TraceSource("CRSessionFSM", "CRSessionFSM")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionFSM", "CRSessionFSM"); /// /// Event handling matrix. It defines what action to take when an event occur. /// [State,Event]=>Action. /// - private EventHandler[,] _stateMachineHandle; - private Queue _clientRemoteSessionStateChangeQueue; + private readonly EventHandler[,] _stateMachineHandle; + private readonly Queue _clientRemoteSessionStateChangeQueue; /// /// Current state of session. /// private RemoteSessionState _state; - private Queue _processPendingEventsQueue + private readonly Queue _processPendingEventsQueue = new Queue(); + // all events raised through the state machine // will be queued in this - private object _syncObject = new object(); + private readonly object _syncObject = new object(); // object for synchronizing access to the above // queue @@ -161,7 +162,7 @@ private void RaiseStateMachineEvents() /// Unique identifier for this state machine. Used /// in tracing. /// - private Guid _id; + private readonly Guid _id; /// /// Handler to be used in cases, where setting the state is the @@ -253,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); @@ -277,7 +275,7 @@ private void SetStateHandler(object sender, RemoteSessionStateMachineEventArgs e Dbg.Assert(_state >= RemoteSessionState.Established, "Client can send a public key only after reaching the Established state"); - Dbg.Assert(_keyExchanged == false, "Client should do key exchange only once"); + Dbg.Assert(!_keyExchanged, "Client should do key exchange only once"); if (_state == RemoteSessionState.Established || _state == RemoteSessionState.EstablishedAndKeyRequested) @@ -338,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); @@ -455,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++) @@ -775,7 +770,7 @@ private void DoFatal(object sender, RemoteSessionStateMachineEventArgs eventArgs #endregion Event Handlers - private void CleanAll() + private static void CleanAll() { } diff --git a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs index 58931499610..cf476cec226 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotepipeline.cs @@ -20,21 +20,21 @@ internal class RemotePipeline : Pipeline #region Private Members private PowerShell _powershell; - private bool _addToHistory; + private readonly bool _addToHistory; private bool _isNested; private bool _isSteppable; - private Runspace _runspace; - private object _syncRoot = new object(); + private readonly Runspace _runspace; + private readonly object _syncRoot = new object(); private bool _disposed = false; private string _historyString; private PipelineStateInfo _pipelineStateInfo = new PipelineStateInfo(PipelineState.NotStarted); - private CommandCollection _commands = new CommandCollection(); - private string _computerName; - private Guid _runspaceId; - private ConnectCommandInfo _connectCmdInfo = null; + private readonly CommandCollection _commands = new CommandCollection(); + private readonly string _computerName; + private readonly Guid _runspaceId; + 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) { @@ -56,7 +56,7 @@ public ExecutionEventQueueItem(PipelineStateInfo pipelineStateInfo, RunspaceAvai public RunspaceAvailability NewRunspaceAvailability; } - private bool _performNestedCheck = true; + private readonly bool _performNestedCheck = true; #endregion Private Members @@ -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 @@ -161,8 +161,12 @@ internal RemotePipeline(RemoteRunspace runspace) /// Pipeline to clone from. /// This constructor is private because this will /// only be called from the copy method - private RemotePipeline(RemotePipeline pipeline) : - this((RemoteRunspace)pipeline.Runspace, null, false, pipeline.IsNested) + private RemotePipeline(RemotePipeline pipeline) + : this( + (RemoteRunspace)pipeline.Runspace, + command: null, + addToHistory: false, + pipeline.IsNested) { _isSteppable = pipeline._isSteppable; @@ -360,12 +364,12 @@ public bool AddToHistory // Stream and Collection go together...a stream wraps // a corresponding collection to support // streaming behavior of the pipeline. - private PSDataCollection _outputCollection; - private PSDataCollectionStream _outputStream; - private PSDataCollection _errorCollection; - private PSDataCollectionStream _errorStream; - private PSDataCollection _inputCollection; - private PSDataCollectionStream _inputStream; + private readonly PSDataCollection _outputCollection; + private readonly PSDataCollectionStream _outputStream; + private readonly PSDataCollection _errorCollection; + private readonly PSDataCollectionStream _errorStream; + private readonly PSDataCollection _inputCollection; + private readonly PSDataCollectionStream _inputStream; /// /// Stream for providing input to PipelineProcessor. Host will write on @@ -595,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: @@ -1014,7 +1018,7 @@ private void Cleanup() /// /// ManualResetEvent which is signaled when pipeline execution is - /// completed/failed/stoped. + /// completed/failed/stopped. /// internal ManualResetEvent PipelineFinishedEvent { get; } @@ -1048,7 +1052,7 @@ internal void DoConcurrentCheck(bool syncCall) RemotePipeline currentPipeline = (RemotePipeline)((RemoteRunspace)_runspace).GetCurrentlyRunningPipeline(); - if (_isNested == false) + if (!_isNested) { if (currentPipeline == null && ((RemoteRunspace)_runspace).RunspaceAvailability != RunspaceAvailability.Busy && @@ -1093,7 +1097,7 @@ internal void DoConcurrentCheck(bool syncCall) return; } - if (syncCall == false) + if (!syncCall) { throw PSTraceSource.NewInvalidOperationException( RunspaceStrings.NestedPipelineInvokeAsync); diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs index a927b49481f..8448007025f 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspace.cs @@ -32,11 +32,11 @@ internal class RemoteRunspace : Runspace, IDisposable { #region Private Members - private List _runningPipelines = new List(); - private object _syncRoot = new object(); + private readonly List _runningPipelines = new List(); + private readonly object _syncRoot = new object(); private RunspaceStateInfo _runspaceStateInfo = new RunspaceStateInfo(RunspaceState.BeforeOpen); - private bool _bSessionStateProxyCallInProgress = false; - private RunspaceConnectionInfo _connectionInfo; + private readonly bool _bSessionStateProxyCallInProgress = false; + private readonly RunspaceConnectionInfo _connectionInfo; private RemoteDebugger _remoteDebugger; private PSPrimitiveDictionary _applicationPrivateData; @@ -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); @@ -156,8 +156,8 @@ internal RemoteRunspace(RunspacePool runspacePool) { // The RemoteRunspace object can only be constructed this way with a RunspacePool that // is in the disconnected state. - if ((runspacePool.RunspacePoolStateInfo.State != RunspacePoolState.Disconnected) || - !(runspacePool.ConnectionInfo is WSManConnectionInfo)) + if (runspacePool.RunspacePoolStateInfo.State != RunspacePoolState.Disconnected + || runspacePool.ConnectionInfo is not WSManConnectionInfo) { throw PSTraceSource.NewInvalidOperationException(RunspaceStrings.InvalidRunspacePool); } @@ -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; } @@ -1113,7 +1111,7 @@ internal void AddToRunningPipelineList(RemotePipeline pipeline) lock (_syncRoot) { - if (_bypassRunspaceStateCheck == false && + if (!_bypassRunspaceStateCheck && _runspaceStateInfo.State != RunspaceState.Opened && _runspaceStateInfo.State != RunspaceState.Disconnected) // Disconnected runspaces can have running pipelines. { @@ -1201,7 +1199,7 @@ internal void DoConcurrentCheckAndAddToRunningPipelines(RemotePipeline pipeline, /// internal override SessionStateProxy GetSessionStateProxy() { - return _sessionStateProxy ?? (_sessionStateProxy = new RemoteSessionStateProxy(this)); + return _sessionStateProxy ??= new RemoteSessionStateProxy(this); } private RemoteSessionStateProxy _sessionStateProxy = null; @@ -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 @@ -1792,7 +1787,7 @@ internal sealed class RemoteDebugger : Debugger, IDisposable { #region Members - private RemoteRunspace _runspace; + private readonly RemoteRunspace _runspace; private PowerShell _psDebuggerCommand; private bool _remoteDebugSupported; private bool _isActive; @@ -1936,17 +1931,17 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC { // Allow the IncompleteParseException to throw so that the console // can handle here strings and continued parsing. - if (re.ErrorRecord.CategoryInfo.Reason == typeof(IncompleteParseException).Name) + if (re.ErrorRecord.CategoryInfo.Reason == nameof(IncompleteParseException)) { throw new IncompleteParseException( - (re.ErrorRecord.Exception != null) ? re.ErrorRecord.Exception.Message : null, + re.ErrorRecord.Exception?.Message, re.ErrorRecord.FullyQualifiedErrorId); } // Allow the RemoteException and InvalidRunspacePoolStateException to propagate so that the host can // clean up the debug session. - if ((re.ErrorRecord.CategoryInfo.Reason == typeof(InvalidRunspacePoolStateException).Name) || - (re.ErrorRecord.CategoryInfo.Reason == typeof(RemoteException).Name)) + if ((re.ErrorRecord.CategoryInfo.Reason == nameof(InvalidRunspacePoolStateException)) || + (re.ErrorRecord.CategoryInfo.Reason == nameof(RemoteException))) { throw new PSRemotingTransportException( (re.ErrorRecord.Exception != null) ? re.ErrorRecord.Exception.Message : string.Empty); @@ -2491,8 +2486,8 @@ internal void CheckStateAndRaiseStopEvent() /// internal bool IsRemoteDebug { - private set; get; + private set; } /// @@ -2660,7 +2655,7 @@ private void ProcessDebuggerStopEvent(DebuggerStopEventArgs args) // Attempt to process debugger stop event on original thread if it // is available (i.e., if it is blocked by EndInvoke). PowerShell powershell = _runspace.RunspacePool.RemoteRunspacePoolInternal.GetCurrentRunningPowerShell(); - AsyncResult invokeAsyncResult = (powershell != null) ? powershell.EndInvokeAsyncResult : null; + AsyncResult invokeAsyncResult = powershell?.EndInvokeAsyncResult; bool invokedOnBlockedThread = false; if ((invokeAsyncResult != null) && (!invokeAsyncResult.IsCompleted)) @@ -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. @@ -208,7 +209,7 @@ public Runspace Runspace public override string ToString() { // PSSession is a PowerShell type name and so should not be localized. - string formatString = "[PSSession]{0}"; + const string formatString = "[PSSession]{0}"; return StringUtil.Format(formatString, Name); } @@ -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; } } @@ -347,9 +349,9 @@ private string GetTransportName() /// /// Shell configuration name. /// Display shell name. - private string GetDisplayShellName(string shell) + private static string GetDisplayShellName(string shell) { - string shellPrefix = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix; + const string shellPrefix = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix; int index = shell.IndexOf(shellPrefix, StringComparison.OrdinalIgnoreCase); return (index == 0) ? shell.Substring(shellPrefix.Length) : shell; @@ -359,6 +361,34 @@ private 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 not 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 8615bf3398a..d36ddff8be3 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs @@ -13,25 +13,25 @@ namespace System.Management.Automation.Remoting /// /// Implements ServerRemoteSessionDataStructureHandler. /// - internal class ClientRemoteSessionDSHandlerImpl : ClientRemoteSessionDataStructureHandler, IDisposable + internal sealed class ClientRemoteSessionDSHandlerImpl : ClientRemoteSessionDataStructureHandler, IDisposable { - [TraceSourceAttribute("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl"); + [TraceSource("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl"); private const string resBaseName = "remotingerroridstrings"; - private BaseClientSessionTransportManager _transportManager; - private ClientRemoteSessionDSHandlerStateMachine _stateMachine; - private ClientRemoteSession _session; - private RunspaceConnectionInfo _connectionInfo; + private readonly BaseClientSessionTransportManager _transportManager; + private readonly ClientRemoteSessionDSHandlerStateMachine _stateMachine; + private readonly ClientRemoteSession _session; + private readonly RunspaceConnectionInfo _connectionInfo; // used for connection redirection. private Uri _redirectUri; private int _maxUriRedirectionCount; private bool _isCloseCalled; - private object _syncObject = new object(); - private PSRemotingCryptoHelper _cryptoHelper; + private readonly object _syncObject = new object(); + private readonly PSRemotingCryptoHelper _cryptoHelper; - private ClientRemoteSession.URIDirectionReported _uriRedirectionHandler; + private readonly ClientRemoteSession.URIDirectionReported _uriRedirectionHandler; internal override BaseClientSessionTransportManager TransportManager { @@ -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() @@ -444,10 +444,7 @@ private void PerformURIRedirectionStep2(System.Uri newURI) } // raise warning to report the redirection - if (_uriRedirectionHandler != null) - { - _uriRedirectionHandler(newURI); - } + _uriRedirectionHandler?.Invoke(newURI); // start a new connection _transportManager.Redirect(newURI, _connectionInfo); @@ -738,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 5f66afdfac1..b7474fe34c2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ConnectPSSession.cs @@ -22,7 +22,7 @@ namespace Microsoft.PowerShell.Commands /// state and returns those PS session objects in the Opened state. One or more /// session objects can be specified for connecting, or a remote computer name can /// be specified and in this case all disconnected remote runspaces found on the - /// remote computer will be be connected and PSSession objects created on the local + /// remote computer will be connected and PSSession objects created on the local /// machine. /// /// The cmdlet can be used in the following ways: @@ -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,15 +487,15 @@ 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; - private ObjectStream _writeStream; - private Collection _retryList; - private PSHost _host; - private QueryRunspaces _queryRunspaces; - private static object s_LockObject = new object(); + private readonly ObjectStream _writeStream; + private readonly Collection _retryList; + private readonly PSHost _host; + private readonly QueryRunspaces _queryRunspaces; + private static readonly object s_LockObject = new object(); internal ConnectRunspaceOperation( PSSession session, @@ -554,10 +569,7 @@ internal override void StartOperation() internal override void StopOperation() { - if (_queryRunspaces != null) - { - _queryRunspaces.StopAllOperations(); - } + _queryRunspaces?.StopAllOperations(); _session.Runspace.StateChanged -= StateCallBackHandler; SendStopComplete(); @@ -691,10 +703,7 @@ private void WriteConnectedPSSession() // and this particular method may be called on a thread that // is different from Pipeline Execution Thread. Hence using // a delegate to perform the WriteObject. - Action outputWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteObject(outSession); - }; + Action outputWriter = (Cmdlet cmdlet) => cmdlet.WriteObject(outSession); _writeStream.ObjectWriter.Write(outputWriter); } } @@ -728,10 +737,7 @@ private void WriteConnectFailed( } ErrorRecord errorRecord = new ErrorRecord(reason, FQEID, ErrorCategory.InvalidOperation, null); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); _writeStream.ObjectWriter.Write(errorWriter); } } @@ -1084,24 +1090,24 @@ private void Dispose(bool disposing) #region Private Members // Collection of PSSessions to be connected. - private Collection _allSessions = new Collection(); + private readonly Collection _allSessions = new Collection(); // Object used to perform network disconnect operations in a limited manner. - private ThrottleManager _throttleManager = new ThrottleManager(); + private readonly ThrottleManager _throttleManager = new ThrottleManager(); // Event indicating that all disconnect operations through the ThrottleManager // are complete. - private ManualResetEvent _operationsComplete = new ManualResetEvent(true); + private readonly ManualResetEvent _operationsComplete = new ManualResetEvent(true); // Object used for querying remote runspaces. - private QueryRunspaces _queryRunspaces = new QueryRunspaces(); + private readonly QueryRunspaces _queryRunspaces = new QueryRunspaces(); // Object to collect output data from multiple threads. - private ObjectStream _stream = new ObjectStream(); + private readonly ObjectStream _stream = new ObjectStream(); // Support for connection retry on failure. - private ThrottleManager _retryThrottleManager = new ThrottleManager(); - private Collection _failedSessions = new Collection(); + private readonly ThrottleManager _retryThrottleManager = new ThrottleManager(); + private readonly Collection _failedSessions = new Collection(); #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index dde4422de45..1d88210e7b6 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; @@ -589,11 +588,12 @@ protected override void ProcessRecord() restartServiceTarget, restartServiceAction, restartWSManRequiredForUI, - runAsCredential != null ? runAsCredential.UserName : null, - runAsCredential != null ? runAsCredential.Password : null, + runAsCredential?.UserName, + runAsCredential?.Password, AccessMode, isSddlSpecified, _configTableSDDL, + noRestart, errorAction }); @@ -641,7 +641,7 @@ protected override void EndProcessing() /// 1. New shell successfully registered. However cannot delete temporary plugin file {0}. /// Reason for failure: {1}. /// - private void DeleteFile(string tmpFileName) + private static void DeleteFile(string tmpFileName) { Dbg.Assert(!string.IsNullOrEmpty(tmpFileName), "tmpFile cannot be null or empty."); @@ -693,7 +693,7 @@ private void DeleteFile(string tmpFileName) /// 2. Cannot write shell configuration data into temporary file {0}. Try again. /// Reason for failure: {1}. /// - private string ConstructTemporaryFile(string pluginContent) + private static string ConstructTemporaryFile(string pluginContent) { // Path.GetTempFileName creates a temporary file whereas GetRandomFileName does not. string tmpFileName = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()) + "psshell.xml"; @@ -708,7 +708,7 @@ private string ConstructTemporaryFile(string pluginContent) try { // Make sure the file is not read only - destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + destfile.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); destfile.Delete(); } catch (FileNotFoundException fnf) @@ -798,7 +798,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d destConfigFilePath = null; StringBuilder initParameters = new StringBuilder(); - bool assemblyAndTypeTokensSet = false; + const bool assemblyAndTypeTokensSet = false; // DISC endpoint if (Path != null) @@ -869,7 +869,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d if (configTable.ContainsKey(ConfigFileConstants.PowerShellVersion)) { - if (isPSVersionSpecified == false) + if (!isPSVersionSpecified) { try { @@ -1071,7 +1071,7 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d } // Default value for PSVersion - if (isPSVersionSpecified == false) + if (!isPSVersionSpecified) { psVersion = PSVersionInfo.PSVersion; } @@ -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); } /// @@ -1753,8 +1753,7 @@ internal static string CreateConditionalACEFromConfig( } StringBuilder conditionalACE = new StringBuilder(); - Hashtable requiredGroupsHash = configTable[ConfigFileConstants.RequiredGroups] as Hashtable; - if (requiredGroupsHash == null) + if (configTable[ConfigFileConstants.RequiredGroups] is not Hashtable requiredGroupsHash) { throw new PSInvalidOperationException(RemotingErrorIdStrings.RequiredGroupsNotHashTable); } @@ -1963,7 +1962,10 @@ public string Name [Parameter(Position = 1, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string AssemblyName { - get { return assemblyName; } + get + { + return assemblyName; + } set { @@ -1985,7 +1987,10 @@ public string AssemblyName [Parameter(ParameterSetName = AssemblyNameParameterSetName)] public string ApplicationBase { - get { return applicationBase; } + get + { + return applicationBase; + } set { @@ -2005,7 +2010,10 @@ public string ApplicationBase [Parameter(Position = 2, Mandatory = true, ParameterSetName = PSSessionConfigurationCommandBase.AssemblyNameParameterSetName)] public string ConfigurationTypeName { - get { return configurationTypeName; } + get + { + return configurationTypeName; + } set { @@ -2054,7 +2062,10 @@ public ApartmentState ThreadApartmentState return ApartmentState.Unknown; } - set { threadAptState = value; } + set + { + threadAptState = value; + } } internal ApartmentState? threadAptState; @@ -2075,7 +2086,10 @@ public PSThreadOptions ThreadOptions return PSThreadOptions.UseCurrentThread; } - set { threadOptions = value; } + set + { + threadOptions = value; + } } internal PSThreadOptions? threadOptions; @@ -2086,7 +2100,10 @@ public PSThreadOptions ThreadOptions [Parameter] public PSSessionConfigurationAccessMode AccessMode { - get { return _accessMode; } + get + { + return _accessMode; + } set { @@ -2125,7 +2142,10 @@ public SwitchParameter UseSharedProcess [Parameter()] public string StartupScript { - get { return configurationScript; } + get + { + return configurationScript; + } set { @@ -2145,7 +2165,10 @@ public string StartupScript [AllowNull] public double? MaximumReceivedDataSizePerCommandMB { - get { return maxCommandSizeMB; } + get + { + return maxCommandSizeMB; + } set { @@ -2172,7 +2195,10 @@ public double? MaximumReceivedDataSizePerCommandMB [AllowNull] public double? MaximumReceivedObjectSizeMB { - get { return maxObjectSizeMB; } + get + { + return maxObjectSizeMB; + } set { @@ -2199,7 +2225,10 @@ public double? MaximumReceivedObjectSizeMB [Parameter()] public string SecurityDescriptorSddl { - get { return sddl; } + get + { + return sddl; + } set { @@ -2229,7 +2258,10 @@ public string SecurityDescriptorSddl [Parameter()] public SwitchParameter ShowSecurityDescriptorUI { - get { return _showUI; } + get + { + return _showUI; + } set { @@ -2281,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); } } @@ -2374,7 +2404,7 @@ public object[] ModulesToImport // Add this check after checking if it a path if (!string.IsNullOrEmpty(modulepath.Trim())) { - if ((modulepath.Contains("\\") || modulepath.Contains(":")) && + if ((modulepath.Contains('\\') || modulepath.Contains(':')) && !(Directory.Exists(modulepath) || File.Exists(modulepath))) { throw new ArgumentException( @@ -2590,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; } @@ -2806,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; } @@ -3230,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; } @@ -3571,56 +3601,6 @@ private void SetQuotas() Hashtable quotas = transportOption.ConstructQuotasAsHashtable(); - int idleTimeOut = 0; - - if (idleTimeOut != 0 && quotas.ContainsKey(WSManConfigurationOption.AttribMaxIdleTimeout)) - { - bool setMaxIdleTimeoutFirst = true; - int maxIdleTimeOut; - - if (LanguagePrimitives.TryConvertTo(quotas[WSManConfigurationOption.AttribMaxIdleTimeout], out maxIdleTimeOut)) - { - int? currentIdleTimeoutms = WSManConfigurationOption.DefaultIdleTimeout; - - // Get the current IdleTimeOut quota value - // - using (System.Management.Automation.PowerShell ps = System.Management.Automation.PowerShell.Create()) - { - ps.AddScript(string.Format(CultureInfo.InvariantCulture, getCurrentIdleTimeoutmsFormat, CodeGeneration.EscapeSingleQuotedStringContent(Name))); - Collection psObjectCollection = ps.Invoke(new object[] { Name }) as Collection; - if (psObjectCollection == null || psObjectCollection.Count != 1) - { - Dbg.Assert(false, "This should never happen. ps.Invoke always return a Collection"); - } - - currentIdleTimeoutms = Convert.ToInt32(psObjectCollection[0].ToString(), CultureInfo.InvariantCulture); - } - - if (currentIdleTimeoutms >= maxIdleTimeOut && currentIdleTimeoutms >= idleTimeOut) - { - setMaxIdleTimeoutFirst = false; - } - } - - ScriptBlock setTimeoutQuotasSb = ScriptBlock.Create( - string.Format(CultureInfo.InvariantCulture, setSessionConfigurationTimeoutQuotasSbFormat, CodeGeneration.EscapeSingleQuotedStringContent(Name))); - setTimeoutQuotasSb.LanguageMode = PSLanguageMode.FullLanguage; - - setTimeoutQuotasSb.InvokeUsingCmdlet( - contextCmdlet: this, - useLocalScope: true, - errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToCurrentErrorPipe, - dollarUnder: AutomationNull.Value, - input: Array.Empty(), - scriptThis: AutomationNull.Value, - args: new object[] { maxIdleTimeOut, idleTimeOut, setMaxIdleTimeoutFirst }); - - // Remove Idle timeout values as we have set them above - // - quotas.Remove(WSManConfigurationOption.AttribMaxIdleTimeout); - quotas.Remove(WSManConfigurationOption.AttribIdleTimeout); - } - setQuotasSb.InvokeUsingCmdlet( contextCmdlet: this, useLocalScope: true, @@ -4013,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 @@ -4218,7 +4198,7 @@ function Enable-PSSessionConfiguration $_ | Enable-PSSessionConfiguration -force $args[0] -sddl $args[1] -isSDDLSpecified $args[2] -queryForSet $args[3] -captionForSet $args[4] -queryForQC $args[5] -captionForQC $args[6] -whatif:$args[7] -confirm:$args[8] -shouldProcessDescForQC $args[9] -setEnabledTarget $args[10] -setEnabledAction $args[11] -skipNetworkProfileCheck $args[12] -noServiceRestart $args[13] "; - private static ScriptBlock s_enablePluginSb; + private static readonly ScriptBlock s_enablePluginSb; #endregion @@ -4230,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; } @@ -4246,7 +4226,7 @@ static EnablePSSessionConfigurationCommand() [ValidateNotNullOrEmpty] public string[] Name { get; set; } - private Collection _shellsToEnable = new Collection(); + private readonly Collection _shellsToEnable = new Collection(); /// /// Property that sets force parameter. This will allow @@ -4505,7 +4485,7 @@ function Disable-PSSessionConfiguration $_ | Disable-PSSessionConfiguration -force $args[0] -whatif:$args[1] -confirm:$args[2] -restartWinRMMessage $args[3] -setEnabledTarget $args[4] -setEnabledAction $args[5] -noServiceRestart $args[6] "; - private static ScriptBlock s_disablePluginSb; + private static readonly ScriptBlock s_disablePluginSb; #endregion @@ -4517,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; } @@ -4533,7 +4513,7 @@ static DisablePSSessionConfigurationCommand() [ValidateNotNullOrEmpty] public string[] Name { get; set; } - private Collection _shellsToDisable = new Collection(); + private readonly Collection _shellsToDisable = new Collection(); /// /// Property that sets force parameter. This will allow @@ -4910,7 +4890,7 @@ function Enable-PSRemoting Enable-PSRemoting -force $args[0] -queryForRegisterDefault $args[1] -captionForRegisterDefault $args[2] -queryForSet $args[3] -captionForSet $args[4] -whatif:$args[5] -confirm:$args[6] -skipNetworkProfileCheck $args[7] -errorMsgUnableToInstallPlugin $args[8] "; - private static ScriptBlock s_enableRemotingSb; + private static readonly ScriptBlock s_enableRemotingSb; #endregion @@ -4924,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; } @@ -5136,7 +5116,7 @@ function Disable-PSRemoting Disable-PSRemoting -force:$args[0] -queryForSet $args[1] -captionForSet $args[2] -restartWinRMMessage $args[3] -whatif:$args[4] -confirm:$args[5] "; - private static ScriptBlock s_disableRemotingSb; + private static readonly ScriptBlock s_disableRemotingSb; #endregion Private Data @@ -5148,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; } @@ -5284,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)) { @@ -5293,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 979912619f2..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,14 +226,17 @@ private bool CheckForDebuggableJob() foreach (var cJob in _job.ChildJobs) { debuggableJobFound = GetJobDebuggable(cJob); - if (debuggableJobFound) { break; } + if (debuggableJobFound) + { + break; + } } } return debuggableJobFound; } - private bool GetJobDebuggable(Job job) + private static bool GetJobDebuggable(Job job) { if (job is IJobDebugger) { @@ -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 0b9cfeb2d6e..74bcbac2c33 100644 --- a/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/DisconnectPSSession.cs @@ -109,7 +109,7 @@ private PSSessionOption PSSessionOption { // no need to lock as the cmdlet parameters will not be assigned // from multiple threads. - return _sessionOption ?? (_sessionOption = new PSSessionOption()); + return _sessionOption ??= new PSSessionOption(); } } @@ -346,7 +346,7 @@ private bool ValidateIdleTimeout(PSSession session) { int idleTimeout = session.Runspace.ConnectionInfo.IdleTimeout; int maxIdleTimeout = session.Runspace.ConnectionInfo.MaxIdleTimeout; - int minIdleTimeout = BaseTransportManager.MinimumIdleTimeout; + const int minIdleTimeout = BaseTransportManager.MinimumIdleTimeout; if (idleTimeout != BaseTransportManager.UseServerDefaultIdleTimeout && (idleTimeout > maxIdleTimeout || idleTimeout < minIdleTimeout)) @@ -363,7 +363,7 @@ private bool ValidateIdleTimeout(PSSession session) return true; } - private string GetLocalhostWithNetworkAccessEnabled(Dictionary psSessions) + private static string GetLocalhostWithNetworkAccessEnabled(Dictionary psSessions) { System.Text.StringBuilder sb = new System.Text.StringBuilder(); @@ -392,10 +392,10 @@ private string GetLocalhostWithNetworkAccessEnabled(Dictionary /// /// Throttle class to perform a remoterunspace disconnect operation. /// - private class DisconnectRunspaceOperation : IThrottleOperation + private sealed class DisconnectRunspaceOperation : IThrottleOperation { - private PSSession _remoteSession; - private ObjectStream _writeStream; + private readonly PSSession _remoteSession; + private readonly ObjectStream _writeStream; internal DisconnectRunspaceOperation(PSSession session, ObjectStream stream) { @@ -481,10 +481,7 @@ private void WriteDisconnectedPSSession() { if (_writeStream.ObjectWriter.IsOpen) { - Action outputWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteObject(_remoteSession); - }; + Action outputWriter = (Cmdlet cmdlet) => cmdlet.WriteObject(_remoteSession); _writeStream.ObjectWriter.Write(outputWriter); } } @@ -506,10 +503,7 @@ private void WriteDisconnectFailed(Exception e = null) Exception reason = new RuntimeException(msg, e); ErrorRecord errorRecord = new ErrorRecord(reason, "PSSessionDisconnectFailed", ErrorCategory.InvalidOperation, _remoteSession); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); _writeStream.ObjectWriter.Write(errorWriter); } } @@ -556,14 +550,14 @@ private void Dispose(bool disposing) #region Private Members // Object used to perform network disconnect operations in a limited manner. - private ThrottleManager _throttleManager = new ThrottleManager(); + private readonly ThrottleManager _throttleManager = new ThrottleManager(); // Event indicating that all disconnect operations through the ThrottleManager // are complete. - private ManualResetEvent _operationsComplete = new ManualResetEvent(true); + private readonly ManualResetEvent _operationsComplete = new ManualResetEvent(true); // Output data stream. - private ObjectStream _stream = new ObjectStream(); + private readonly ObjectStream _stream = new ObjectStream(); #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs b/src/System.Management.Automation/engine/remoting/commands/EnterPSHostProcessCommand.cs index e99f77db796..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 @@ -292,7 +289,7 @@ private Runspace CreateNamedPipeRunspace(NamedPipeConnectionInfo connectionInfo) return remoteRunspace; } - private void PrepareRunspace(Runspace runspace) + private static void PrepareRunspace(Runspace runspace) { string promptFn = StringUtil.Format(RemotingErrorIdStrings.EnterPSHostProcessPrompt, @"function global:prompt { """, @@ -318,22 +315,19 @@ private 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) @@ -377,7 +371,7 @@ private Process GetProcessByName(string name) private void VerifyProcess(Process process) { - if (process.Id == Process.GetCurrentProcess().Id) + if (process.Id == Environment.ProcessId) { ThrowTerminatingError( new ErrorRecord( @@ -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; @@ -574,7 +568,7 @@ protected override void EndProcessing() #region Private Methods - private int[] GetProcIdsFromProcs(Process[] processes) + private static int[] GetProcIdsFromProcs(Process[] processes) { List returnIds = new List(); foreach (Process process in processes) @@ -585,7 +579,7 @@ private int[] GetProcIdsFromProcs(Process[] processes) return returnIds.ToArray(); } - private int[] GetProcIdsFromNames(string[] names) + private static int[] GetProcIdsFromNames(string[] names) { if ((names == null) || (names.Length == 0)) { @@ -599,9 +593,22 @@ private 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) + { + continue; + } + + try + { + if (namePattern.IsMatch(proc.ProcessName)) + { + returnIds.Add(proc.Id); + } + } + catch (InvalidOperationException) { - returnIds.Add(proc.Id); + // 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. + } } } } @@ -747,38 +766,22 @@ public sealed class PSHostProcessInfo /// /// Name of process. /// - public string ProcessName - { - get; - private set; - } + public string ProcessName { get; } /// /// Id of process. /// - public int ProcessId - { - get; - private set; - } + public int ProcessId { get; } /// /// Name of PowerShell AppDomain in process. /// - public string AppDomainName - { - get; - private set; - } + public string AppDomainName { get; } /// /// Main window title of the process. /// - public string MainWindowTitle - { - get; - private set; - } + public string MainWindowTitle { get; } #endregion @@ -812,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) { @@ -847,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 daef292017f..e340a1acbdb 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 @@ -226,7 +226,7 @@ public override PSCredential Credential [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public override int Port { get @@ -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, @@ -347,14 +347,14 @@ public override string ApplicationName [Parameter(ParameterSetName = InvokeCommandCommand.FilePathContainerIdParameterSet)] public override int ThrottleLimit { - set + get { - base.ThrottleLimit = value; + return base.ThrottleLimit; } - get + set { - return base.ThrottleLimit; + base.ThrottleLimit = value; } } @@ -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 @@ -972,7 +1015,7 @@ protected override void BeginProcessing() // of this bug in Win8 where not responding can occur during data piping. // We are reverting to Win7 behavior for {icm | icm} and {proxycommand | proxycommand} // cases. For ICM | % ICM case, we are using remote steppable pipeline. - if ((MyInvocation != null) && (MyInvocation.PipelinePosition == 1) && (MyInvocation.ExpectingInput == false)) + if ((MyInvocation != null) && (MyInvocation.PipelinePosition == 1) && !MyInvocation.ExpectingInput) { PSPrimitiveDictionary table = (object)runspaceInfo.ApplicationPrivateData[PSVersionInfo.PSVersionTableName] as PSPrimitiveDictionary; if (table != null) @@ -983,7 +1026,7 @@ protected override void BeginProcessing() { // In order to support foreach remoting properly ( icm | % { icm } ), the server must // be using protocol version 2.2. Otherwise, we skip this and assume the old behavior. - if (version >= RemotingConstants.ProtocolVersionWin8RTM) + if (version >= RemotingConstants.ProtocolVersion_2_2) { // Suppress collection behavior _needToCollect = false; @@ -1007,8 +1050,7 @@ protected override void BeginProcessing() // create collection of input writers here foreach (IThrottleOperation operation in Operations) { - ExecutionCmdletHelperRunspace ecHelper = operation as ExecutionCmdletHelperRunspace; - if (ecHelper == null) + if (operation is not ExecutionCmdletHelperRunspace ecHelper) { // either all the operations will be of type ExecutionCmdletHelperRunspace // or not...there is no mix. @@ -1421,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) @@ -1441,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(); } } } @@ -1876,7 +1912,7 @@ private void StartProgressBar( this.Host); } - private void StopProgressBar( + private static void StopProgressBar( long sourceId) { s_RCProgress.StopProgress(sourceId); @@ -1955,7 +1991,7 @@ private void DetermineThrowStatementBehavior() /// Process the stream object before writing it in the specified collection. /// /// Stream object to process. - private void PreProcessStreamObject(PSStreamObject streamObject) + private static void PreProcessStreamObject(PSStreamObject streamObject) { ErrorRecord errorRecord = streamObject.Value as ErrorRecord; @@ -1984,7 +2020,7 @@ private void PreProcessStreamObject(PSStreamObject streamObject) private ThrottleManager _throttleManager = new ThrottleManager(); // throttle manager for handling all throttling operations - private ManualResetEvent _operationsComplete = new ManualResetEvent(true); + private readonly ManualResetEvent _operationsComplete = new ManualResetEvent(true); private ManualResetEvent _disconnectComplete; // the initial state is true because when no // operations actually take place as in case of a @@ -2002,17 +2038,17 @@ private void PreProcessStreamObject(PSStreamObject streamObject) private const string InProcParameterSet = "InProcess"; - private PSDataCollection _input = new PSDataCollection(); + private readonly PSDataCollection _input = new PSDataCollection(); private bool _needToCollect = false; private bool _needToStartSteppablePipelineOnServer = false; private bool _clearInvokeCommandOnRunspace = false; - private List _inputWriters = new List(); - private object _jobSyncObject = new object(); + private readonly List _inputWriters = new List(); + private readonly object _jobSyncObject = new object(); private bool _nojob = false; - private Guid _instanceId = Guid.NewGuid(); + private readonly Guid _instanceId = Guid.NewGuid(); private bool _propagateErrors = false; - private static RobustConnectionProgress s_RCProgress = new RobustConnectionProgress(); + private static readonly RobustConnectionProgress s_RCProgress = new RobustConnectionProgress(); internal static readonly string RemoteJobType = "RemoteJob"; @@ -2048,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(); @@ -2092,14 +2125,14 @@ namespace System.Management.Automation.Internal internal class RobustConnectionProgress { private System.Management.Automation.Host.PSHost _psHost; - private string _activity; + private readonly string _activity; private string _status; private int _secondsTotal; private int _secondsRemaining; private ProgressRecord _progressRecord; private long _sourceId; private bool _progressIsRunning; - private object _syncObject; + private readonly object _syncObject; private Timer _updateTimer; /// @@ -2134,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 d06eabf25a3..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) { @@ -132,9 +126,9 @@ internal Dictionary Dictionary #region Private Members - private Dictionary _repository = new Dictionary(); - private object _syncObject = new object(); // object for synchronization - private string _identifier; + private readonly Dictionary _repository = new Dictionary(); + private readonly object _syncObject = new object(); // object for synchronization + private readonly string _identifier; #endregion Private Members } diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationFile.cs index 8a7a9b88b88..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 @@ -857,7 +856,7 @@ protected override void ProcessRecord() { if (Full) { - string exampleModulesToImport = "'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }"; + const string exampleModulesToImport = "'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }"; result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.ModulesToImport, RemotingErrorIdStrings.DISCModulesToImportComment, exampleModulesToImport, streamWriter, true)); } } @@ -967,7 +966,7 @@ protected override void ProcessRecord() ThrowTerminatingError(e.ErrorRecord); } - if ((hashtable[ConfigFileConstants.FunctionValueToken] as ScriptBlock) == null) + if (hashtable[ConfigFileConstants.FunctionValueToken] is not ScriptBlock) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCKeyMustBeScriptBlock, ConfigFileConstants.FunctionValueToken, ConfigFileConstants.FunctionDefinitions, _path)); @@ -1105,7 +1104,7 @@ protected override void ProcessRecord() SessionConfigurationUtils.CombineStringArray(_assembliesToLoad), streamWriter, isExample)); } - result.Append("}"); + result.Append('}'); streamWriter.Write(result.ToString()); } @@ -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 { @@ -1621,7 +1619,7 @@ protected override void ProcessRecord() // Modules to import if (_modulesToImport == null) { - string exampleModulesToImport = "'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }"; + const string exampleModulesToImport = "'MyCustomModule', @{ ModuleName = 'MyCustomModule'; ModuleVersion = '1.0.0.0'; GUID = '4d30d5f0-cb16-4898-812d-f20a6c596bdf' }"; result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.ModulesToImport, RemotingErrorIdStrings.DISCModulesToImportComment, exampleModulesToImport, streamWriter, true)); } else @@ -1714,7 +1712,7 @@ protected override void ProcessRecord() ThrowTerminatingError(e.ErrorRecord); } - if ((hashtable[ConfigFileConstants.FunctionValueToken] as ScriptBlock) == null) + if (hashtable[ConfigFileConstants.FunctionValueToken] is not ScriptBlock) { PSArgumentException e = new PSArgumentException(StringUtil.Format(RemotingErrorIdStrings.DISCKeyMustBeScriptBlock, ConfigFileConstants.FunctionValueToken, ConfigFileConstants.FunctionDefinitions, _path)); @@ -1837,7 +1835,7 @@ protected override void ProcessRecord() result.Append(SessionConfigurationUtils.ConfigFragment(ConfigFileConstants.AssembliesToLoad, RemotingErrorIdStrings.DISCAssembliesToLoadComment, SessionConfigurationUtils.CombineStringArray(_assembliesToLoad), streamWriter, isExample)); - result.Append("}"); + result.Append('}'); streamWriter.Write(result.ToString()); } @@ -1855,7 +1853,7 @@ protected override void ProcessRecord() /// /// Utility methods for configuration file commands. /// - internal class SessionConfigurationUtils + internal static class SessionConfigurationUtils { /// /// This routine builds a fragment of the config file @@ -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,14 +1943,14 @@ 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); sb.AppendFormat("{0," + (4 * (indent + 1)) + "}", string.Empty); sb.Append(QuoteName(key)); sb.Append(" = "); - if ((table[key] as ScriptBlock) != null) + if (table[key] is ScriptBlock) { sb.Append(WrapScriptBlock(table[key].ToString())); continue; diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs index c1b3b3b7326..fdd9d5fc6fe 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionConfigurationOptionCommand.cs @@ -441,7 +441,7 @@ internal override Hashtable ConstructOptionsAsHashtable() [OutputType(typeof(WSManConfigurationOption))] public sealed class NewPSTransportOptionCommand : PSCmdlet { - private WSManConfigurationOption _option = new WSManConfigurationOption(); + private readonly WSManConfigurationOption _option = new WSManConfigurationOption(); /// /// MaxIdleTimeoutSec. diff --git a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs index 5ad0df8910c..bb7641d2e5b 100644 --- a/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/NewPSSessionOptionCommand.cs @@ -48,7 +48,7 @@ public int MaximumRedirection public SwitchParameter NoCompression { get; set; } /// - /// If true then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server + /// If then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server /// which can result in a faster session creation time. This option won't have any effect if the remote machine has /// already loaded the profile (i.e. in another session). /// @@ -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 de046236536..ee06dc22130 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; @@ -146,7 +147,7 @@ internal string GetMessage(string resourceString, params object[] args) #region Private Members - private static string s_LOCALHOST = "localhost"; + private static readonly string s_LOCALHOST = "localhost"; // private PSETWTracer tracer = PSETWTracer.GetETWTracer(PSKeyword.Cmdlets); @@ -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 @@ -572,7 +606,7 @@ public virtual PSCredential Credential /// [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public virtual int Port { get; set; } /// @@ -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, @@ -620,7 +654,7 @@ public virtual string ApplicationName [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ContainerIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMIdParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] - public virtual int ThrottleLimit { set; get; } = 0; + public virtual int ThrottleLimit { get; set; } = 0; /// /// A complete URI(s) specified for the remote computer and shell to @@ -669,7 +703,10 @@ public virtual PSSessionOption SessionOption return _sessionOption; } - set { _sessionOption = value; } + set + { + _sessionOption = value; + } } private PSSessionOption _sessionOption; @@ -707,7 +744,10 @@ public virtual AuthenticationMechanism Authentication [Parameter(ParameterSetName = NewPSSessionCommand.UriParameterSet)] public virtual string CertificateThumbprint { - get { return _thumbPrint; } + get + { + return _thumbPrint; + } set { @@ -755,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 @@ -784,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 @@ -850,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 @@ -896,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)) @@ -909,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; @@ -949,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( @@ -1442,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); @@ -1465,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); @@ -1624,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; } @@ -1673,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; } @@ -1858,9 +1923,7 @@ internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) // the array-form using values if all UsingExpressions are in the same scope, otherwise, we handle the UsingExpression as // if the remote end is PSv2. string serverPsVersion = GetRemoteServerPsVersion(remoteRunspace); - System.Management.Automation.PowerShell powershellToUse = (serverPsVersion == PSv2) - ? GetPowerShellForPSv2() - : GetPowerShellForPSv3OrLater(serverPsVersion); + System.Management.Automation.PowerShell powershellToUse = GetPowerShellForPSv3OrLater(serverPsVersion); Pipeline pipeline = remoteRunspace.CreatePipeline(powershellToUse.Commands.Commands[0].CommandText, true); @@ -1879,11 +1942,11 @@ internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) /// /// Check the powershell version of the remote server. /// - private string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) + private static string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) { - if (remoteRunspace.ConnectionInfo is NewProcessConnectionInfo) + if (remoteRunspace.ConnectionInfo is not WSManConnectionInfo) { - // This is for Start-Job. The remote end is actually a child local powershell process, so it must be PSv5 or later + // All transport types except for WSManConnectionInfo work with 5.1 or later. return PSv5OrLater; } @@ -1892,45 +1955,25 @@ private string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) { // The remote runspace is not opened yet, or it's disconnected before the private data is retrieved. // In this case we cannot validate if the remote server is running PSv5 or later, so for safety purpose, - // we will handle the $using expressions as if the remote server is PSv2. - return PSv2; + // we will handle the $using expressions as if the remote server is PSv3Orv4. + return PSv3Orv4; } - // Unfortunately, the PSVersion value in the private data from PSv3 and PSv4 server is always 2.0. - // This got fixed in PSv5, so a PSv5 server will return 5.0. That means we need other way to tell - // if the remote server is PSv2 or PSv3+. After PSv3, remote runspace supports connect/disconnect, - // so we can use it to differentiate PSv2 from PSv3+. - if (remoteRunspace.CanDisconnect) - { - Version serverPsVersion = null; - PSPrimitiveDictionary.TryPathGet( - psApplicationPrivateData, - out serverPsVersion, - PSVersionInfo.PSVersionTableName, - PSVersionInfo.PSVersionName); - - if (serverPsVersion != null) - { - return serverPsVersion.Major >= 5 ? PSv5OrLater : PSv3Orv4; - } - - // The private data is available but we failed to get the server powershell version. - // This should never happen, but in case it happens, handle the $using expressions - // as if the remote server is PSv2. - Dbg.Assert(false, "Application private data is available but we failed to get the server powershell version. This should never happen."); - } + PSPrimitiveDictionary.TryPathGet( + psApplicationPrivateData, + out Version serverPsVersion, + PSVersionInfo.PSVersionTableName, + PSVersionInfo.PSVersionName); - return PSv2; + // PSv5 server will return 5.0 whereas older versions will always be 2.0. As we don't care about v2 + // anymore we can use a simple ternary check here to differenciate v5 using behaviour vs v3/4. + return serverPsVersion != null && serverPsVersion.Major >= 5 ? PSv5OrLater : PSv3Orv4; } /// /// 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 @@ -1998,7 +2041,6 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) /// private const string PSv5OrLater = "PSv5OrLater"; private const string PSv3Orv4 = "PSv3Orv4"; - private const string PSv2 = "PSv2"; private System.Management.Automation.PowerShell _powershellV2; private System.Management.Automation.PowerShell _powershellV3; @@ -2248,7 +2290,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; @@ -2302,7 +2344,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) @@ -2353,7 +2395,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) { @@ -2427,15 +2470,12 @@ private List GetUsingVariableValues(List paramUsi /// /// /// A list of UsingExpressionAsts ordered by the StartOffset. - private List GetUsingVariables(ScriptBlock localScriptBlock) + 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.FindAllUsingExpressionExceptForWorkflow(localScriptBlock.Ast); - return allUsingExprs.Select(usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList(); + var allUsingExprs = UsingExpressionAstSearcher.FindAllUsingExpressions(localScriptBlock.Ast); + return allUsingExprs.Select(static usingExpr => UsingExpressionAst.ExtractUsingVariable((UsingExpressionAst)usingExpr)).ToList(); } #endregion "UsingExpression Utilities" @@ -3003,9 +3043,9 @@ private Dictionary GetMatchingRunspacesByVMNameContainerId(bool var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => (supportWildChar ? inputNamePattern.IsMatch(session.VMName) : inputName.Equals(session.ContainerId)) && - ((sessionNamePattern == null) ? true : sessionNamePattern.IsMatch(session.Name)) && + (sessionNamePattern == null || sessionNamePattern.IsMatch(session.Name)) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && - ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && + (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == computerType)) .ToList(); @@ -3063,7 +3103,7 @@ private Dictionary GetMatchingRunspacesByVMNameContainerIdSessi : inputName.Equals(session.ContainerId)) && sessionInstanceId.Equals(session.InstanceId) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && - ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && + (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == computerType)) .ToList(); @@ -3106,9 +3146,9 @@ private Dictionary GetMatchingRunspacesByVMId(bool writeobject, var matchingRunspaceInfos = remoteRunspaceInfos .Where(session => vmId.Equals(session.VMId) && - ((sessionNamePattern == null) ? true : sessionNamePattern.IsMatch(session.Name)) && + (sessionNamePattern == null || sessionNamePattern.IsMatch(session.Name)) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && - ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && + (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == TargetMachineType.VirtualMachine)) .ToList(); @@ -3143,7 +3183,7 @@ private Dictionary GetMatchingRunspacesByVMIdSessionInstanceId( .Where(session => vmId.Equals(session.VMId) && sessionInstanceId.Equals(session.InstanceId) && QueryRunspaces.TestRunspaceState(session.Runspace, filterState) && - ((configurationNamePattern == null) ? true : configurationNamePattern.IsMatch(session.ConfigurationName)) && + (configurationNamePattern == null || configurationNamePattern.IsMatch(session.ConfigurationName)) && (session.ComputerType == TargetMachineType.VirtualMachine)) .ToList(); @@ -3257,8 +3297,8 @@ internal Exception InternalException /// internal Runspace PipelineRunspace { - set; get; + set; } #region Runspace Debug @@ -3446,10 +3486,7 @@ private void RaiseOperationCompleteEvent(EventArgs baseEventArgs) OperationState.StopComplete; operationStateEventArgs.BaseEvent = baseEventArgs; - if (OperationComplete != null) - { - OperationComplete.SafeInvoke(this, operationStateEventArgs); - } + OperationComplete?.SafeInvoke(this, operationStateEventArgs); } } @@ -3472,7 +3509,7 @@ internal class ExecutionCmdletHelperComputerName : ExecutionCmdletHelper /// Determines if the command should be invoked and then disconnect the /// remote runspace from the client. /// - private bool _invokeAndDisconnect; + private readonly bool _invokeAndDisconnect; /// /// The remote runspace created using the computer name @@ -3644,11 +3681,7 @@ private void HandlePipelineStateChanged(object sender, case PipelineState.Completed: case PipelineState.Stopped: case PipelineState.Failed: - if (RemoteRunspace != null) - { - RemoteRunspace.CloseAsync(); - } - + RemoteRunspace?.CloseAsync(); break; } } @@ -3919,9 +3952,9 @@ internal Collection GetDisconnectedSessions(Collection - /// If true then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server + /// If then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server /// which can result in a faster session creation time. This option won't have any effect if the remote machine has /// already loaded the profile (i.e. in another session). /// @@ -4353,7 +4383,10 @@ public PSSessionOption() /// public AuthenticationMechanism ProxyAuthentication { - get { return _proxyAuthentication; } + get + { + return _proxyAuthentication; + } set { diff --git a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs index a7751e0138d..bf456e85db2 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PushRunspaceCommand.cs @@ -39,7 +39,7 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet /// /// Disable ThrottleLimit parameter inherited from base class. /// - public new int ThrottleLimit { set { } get { return 0; } } + public new int ThrottleLimit { get { return 0; } set { } } private ObjectStream _stream; private RemoteRunspace _tempRunspace; @@ -55,9 +55,27 @@ public class EnterPSSessionCommand : PSRemotingBaseCmdlet /// [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; @@ -651,10 +672,7 @@ private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourc private void HandleURIDirectionReported(object sender, RemoteDataEventArgs eventArgs) { string message = StringUtil.Format(RemotingErrorIdStrings.URIRedirectWarningToHost, eventArgs.Data.OriginalString); - Action streamObject = delegate (Cmdlet cmdlet) - { - cmdlet.WriteWarning(message); - }; + Action streamObject = (Cmdlet cmdlet) => cmdlet.WriteWarning(message); _stream.Write(streamObject); } @@ -794,15 +812,13 @@ private RemoteRunspace GetRunspaceMatchingCondition( /// private RemoteRunspace GetRunspaceMatchingRunspaceId(Guid remoteRunspaceId) { - Predicate condition = delegate (PSSession info) - { - return info.InstanceId == remoteRunspaceId; - }; - PSRemotingErrorId tooFew = PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedRunspaceId; - PSRemotingErrorId tooMany = PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedRunspaceId; - string tooFewResourceString = RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedRunspaceId; - string tooManyResourceString = RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedRunspaceId; - return GetRunspaceMatchingCondition(condition, tooFew, tooMany, tooFewResourceString, tooManyResourceString, remoteRunspaceId); + return GetRunspaceMatchingCondition( + condition: info => info.InstanceId == remoteRunspaceId, + tooFew: PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedRunspaceId, + tooMany: PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedRunspaceId, + tooFewResourceString: RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedRunspaceId, + tooManyResourceString: RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedRunspaceId, + errorArgument: remoteRunspaceId); } /// @@ -810,15 +826,13 @@ private RemoteRunspace GetRunspaceMatchingRunspaceId(Guid remoteRunspaceId) /// private RemoteRunspace GetRunspaceMatchingSessionId(int sessionId) { - Predicate condition = delegate (PSSession info) - { - return info.Id == sessionId; - }; - PSRemotingErrorId tooFew = PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedSessionId; - PSRemotingErrorId tooMany = PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedSessionId; - string tooFewResourceString = RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedSessionId; - string tooManyResourceString = RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedSessionId; - return GetRunspaceMatchingCondition(condition, tooFew, tooMany, tooFewResourceString, tooManyResourceString, sessionId); + return GetRunspaceMatchingCondition( + condition: info => info.Id == sessionId, + tooFew: PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedSessionId, + tooMany: PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedSessionId, + tooFewResourceString: RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedSessionId, + tooManyResourceString: RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedSessionId, + errorArgument: sessionId); } /// @@ -826,16 +840,13 @@ private RemoteRunspace GetRunspaceMatchingSessionId(int sessionId) /// private RemoteRunspace GetRunspaceMatchingName(string name) { - Predicate condition = delegate (PSSession info) - { - // doing case-insensitive match for session name - return info.Name.Equals(name, StringComparison.OrdinalIgnoreCase); - }; - PSRemotingErrorId tooFew = PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedName; - PSRemotingErrorId tooMany = PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedName; - string tooFewResourceString = RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedName; - string tooManyResourceString = RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedName; - return GetRunspaceMatchingCondition(condition, tooFew, tooMany, tooFewResourceString, tooManyResourceString, name); + return GetRunspaceMatchingCondition( + condition: info => info.Name.Equals(name, StringComparison.OrdinalIgnoreCase), + tooFew: PSRemotingErrorId.RemoteRunspaceNotAvailableForSpecifiedName, + tooMany: PSRemotingErrorId.RemoteRunspaceHasMultipleMatchesForSpecifiedName, + tooFewResourceString: RemotingErrorIdStrings.RemoteRunspaceNotAvailableForSpecifiedName, + tooManyResourceString: RemotingErrorIdStrings.RemoteRunspaceHasMultipleMatchesForSpecifiedName, + errorArgument: name); } private Job FindJobForRunspace(Guid id) @@ -1014,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( @@ -1093,7 +1104,7 @@ private RemoteRunspace GetRunspaceForVMSession() /// /// Create temporary remote runspace. /// - private RemoteRunspace CreateTemporaryRemoteRunspaceForPowerShellDirect(PSHost host, RunspaceConnectionInfo connectionInfo) + private static RemoteRunspace CreateTemporaryRemoteRunspaceForPowerShellDirect(PSHost host, RunspaceConnectionInfo connectionInfo) { // Create and open the runspace. TypeTable typeTable = TypeTable.LoadDefaultTypeFiles(); @@ -1272,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 aa804f68691..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,10 +278,13 @@ public SwitchParameter AutoRemoveJob /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteEvents { - get { return _writeStateChangedEvents; } + get + { + return _writeStateChangedEvents; + } set { @@ -291,10 +294,13 @@ public SwitchParameter WriteEvents /// /// - [Parameter()] + [Parameter] public SwitchParameter WriteJobInResults { - get { return _outputJobFirst; } + get + { + return _outputJobFirst; + } set { @@ -768,7 +774,7 @@ private void WriteJobResults(Job job) // There is a bug in V2 that only remoting jobs work // with Receive-Job. This is being fixed - if (!(job is Job2) && job.UsesResultsCollection) + if (job is not Job2 && job.UsesResultsCollection) { // extract results and handle them Collection results = ReadAll(job.Results); @@ -817,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); @@ -829,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); @@ -841,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); @@ -853,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); @@ -865,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); } } @@ -1059,10 +1050,7 @@ private void AggregateResultsFromJob(Job job) { lock (_syncObject) { - if (_outputProcessingNotification == null) - { - _outputProcessingNotification = new OutputProcessingState(); - } + _outputProcessingNotification ??= new OutputProcessingState(); } } @@ -1175,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 500c5032a49..67477e5b36f 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); @@ -1115,7 +1123,7 @@ private void ConnectSessionToJob(PSSession session, PSRemotingJob job = null) /// Session to connect. /// Optional exception object. /// Connected session or null. - private PSSession ConnectSession(PSSession session, out Exception ex) + private static PSSession ConnectSession(PSSession session, out Exception ex) { ex = null; @@ -1158,8 +1166,7 @@ private PSSession ConnectSession(PSSession session, out Exception ex) /// PSSession disconnected runspace object. private PSSession TryGetSessionFromServer(PSSession session) { - RemoteRunspace remoteRunspace = session.Runspace as RemoteRunspace; - if (remoteRunspace == null) + if (session.Runspace is not RemoteRunspace remoteRunspace) { return null; } @@ -1303,7 +1310,7 @@ private void WriteInvalidArgumentError(PSRemotingErrorId errorId, string resourc private RemotePipeline _remotePipeline; private Job _job; private ManualResetEvent _stopPipelineReceive; - private object _syncObject = new object(); + private readonly object _syncObject = new object(); #endregion } @@ -1326,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 2eed427a6ff..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) { @@ -540,7 +566,7 @@ internal List FindJobsMatchingByFilter(bool writeobject) /// /// /// - private bool FindJobsMatchingByFilterHelper(List matches, List jobsToSearch) + private static bool FindJobsMatchingByFilterHelper(List matches, List jobsToSearch) { // check that filter only has job properties // if so, filter on one at a time using helpers. @@ -558,7 +584,10 @@ private bool FindJobsMatchingByFilterHelper(List matches, List jobsToS 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)) @@ -1011,7 +1042,7 @@ private void HandleStopJobCompleted(object sender, AsyncCompletedEventArgs event #region Private Members - private HashSet _pendingJobs = new HashSet(); + private readonly HashSet _pendingJobs = new HashSet(); private readonly ManualResetEvent _waitForJobs = new ManualResetEvent(false); private readonly Dictionary> _cleanUpActions = @@ -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 29e6640279f..9913ec64ced 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 @@ -277,7 +277,7 @@ public override string ConfigurationName /// /// Overriding to suppress this parameter. /// - public override Int32 ThrottleLimit + public override int ThrottleLimit { 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; } /// @@ -505,14 +505,19 @@ public virtual ScriptBlock InitializationScript [ValidateNotNullOrEmpty] public virtual 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 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; } @@ -596,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( @@ -641,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 f023ca168a7..6964a1154b5 100644 --- a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs @@ -46,7 +46,7 @@ public class WaitJobCommand : JobCmdletBase, IDisposable /// [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, Int32.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int Timeout { get @@ -105,10 +105,7 @@ private void InvokeEndProcessingAction() } // Invoke action outside lock. - if (endProcessingAction != null) - { - endProcessingAction(); - } + endProcessingAction?.Invoke(); } private void CleanUpEndProcessing() @@ -247,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 2a3c92dfcf3..22af9adf9ff 100644 --- a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs @@ -8,6 +8,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; using Dbg = System.Management.Automation.Diagnostics; @@ -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, @@ -78,7 +79,10 @@ public class GetPSSessionCommand : PSRunspaceCmdlet, IDisposable ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] public string ApplicationName { - get { return _appName; } + get + { + return _appName; + } set { @@ -158,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; } @@ -199,10 +203,13 @@ public override Guid[] InstanceId [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] - [Credential()] + [Credential] public PSCredential Credential { - get { return _psCredential; } + get + { + return _psCredential; + } set { @@ -223,7 +230,10 @@ public PSCredential Credential [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] public AuthenticationMechanism Authentication { - get { return _authentication; } + get + { + return _authentication; + } set { @@ -245,7 +255,10 @@ public AuthenticationMechanism Authentication [Parameter(ParameterSetName = GetPSSessionCommand.ConnectionUriInstanceIdParameterSet)] public string CertificateThumbprint { - get { return _thumbprint; } + get + { + return _thumbprint; + } set { @@ -270,7 +283,7 @@ public string CertificateThumbprint /// [Parameter(ParameterSetName = GetPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public int Port { get; set; } /// @@ -329,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; } /// @@ -525,10 +549,10 @@ public void Dispose() #region Private Members // Object used for querying remote runspaces. - private QueryRunspaces _queryRunspaces = new QueryRunspaces(); + private readonly QueryRunspaces _queryRunspaces = new QueryRunspaces(); // Object to collect output data from multiple threads. - private ObjectStream _stream = new ObjectStream(); + private readonly ObjectStream _stream = new ObjectStream(); #endregion } diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index 5645272bbe2..60ca0c35e50 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -88,10 +88,13 @@ public class NewPSSessionCommand : PSRemotingBaseCmdlet, IDisposable [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = PSRemotingBaseCmdlet.VMNameParameterSet)] - [Credential()] + [Credential] public override PSCredential Credential { - get { return base.Credential; } + get + { + return base.Credential; + } set { @@ -126,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; } @@ -264,7 +267,16 @@ protected override void ProcessRecord() case NewPSSessionCommand.UseWindowsPowerShellParameterSet: { - remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + if (UseWindowsPowerShell) + { + remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + } + else + { + // When -UseWindowsPowerShell:$false is explicitly specified, + // fall back to the default ComputerName parameter set behavior + goto case NewPSSessionCommand.ComputerNameParameterSet; + } } break; @@ -382,11 +394,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 @@ -397,10 +405,7 @@ private void OnRunspacePSEventReceived(object sender, PSEventArgs e) private void HandleURIDirectionReported(object sender, RemoteDataEventArgs eventArgs) { string message = StringUtil.Format(RemotingErrorIdStrings.URIRedirectWarningToHost, eventArgs.Data.OriginalString); - Action warningWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteWarning(message); - }; + Action warningWriter = (Cmdlet cmdlet) => cmdlet.WriteWarning(message); _stream.Write(warningWriter); } @@ -450,10 +455,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s this.RunspaceRepository.Add(remoteRunspaceInfo); - Action outputWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteObject(remoteRunspaceInfo); - }; + Action outputWriter = (Cmdlet cmdlet) => cmdlet.WriteObject(remoteRunspaceInfo); if (writer.IsOpen) { writer.Write(outputWriter); @@ -527,16 +529,13 @@ 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, _defaultFQEID); - if (WSManNativeApi.ERROR_WSMAN_NO_LOGON_SESSION_EXIST == transErrorCode) + if (transErrorCode == WSManNativeApi.ERROR_WSMAN_NO_LOGON_SESSION_EXIST) { errorDetails += System.Environment.NewLine + string.Format(System.Globalization.CultureInfo.CurrentCulture, RemotingErrorIdStrings.RemotingErrorNoLogonSessionExist); } @@ -546,7 +545,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s ErrorCategory.OpenError, null, null, null, null, null, errorDetails, null); - Action errorWriter = delegate (Cmdlet cmdlet) + Action errorWriter = (Cmdlet cmdlet) => { // // In case of PSDirectException, we should output the precise error message @@ -589,10 +588,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s (connectionUri != null) ? connectionUri.AbsoluteUri : string.Empty); - Action verboseWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteVerbose(message); - }; + Action verboseWriter = (Cmdlet cmdlet) => cmdlet.WriteVerbose(message); if (writer.IsOpen) { writer.Write(verboseWriter); @@ -607,10 +603,7 @@ private void HandleRunspaceStateChanged(object sender, OperationStateEventArgs s "PSSessionStateClosed", ErrorCategory.OpenError, remoteRunspace); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); if (writer.IsOpen) { writer.Write(errorWriter); @@ -656,11 +649,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; } @@ -673,7 +666,7 @@ private List CreateRunspacesWhenRunspaceParameterSpecified() if (originalWSManConnectionInfo != null) { newWSManConnectionInfo = originalWSManConnectionInfo.Copy(); - newWSManConnectionInfo.EnableNetworkAccess = (newWSManConnectionInfo.EnableNetworkAccess || EnableNetworkAccess) ? true : false; + newWSManConnectionInfo.EnableNetworkAccess = newWSManConnectionInfo.EnableNetworkAccess || EnableNetworkAccess; newConnectionInfo = newWSManConnectionInfo; } else @@ -716,10 +709,7 @@ private List CreateRunspacesWhenRunspaceParameterSpecified() ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceFailed", ErrorCategory.InvalidArgument, remoteRunspaceInfo); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); writer.Write(errorWriter); } } @@ -853,10 +843,7 @@ private List CreateRunspacesWhenComputerNameParameterSpecified() ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceFailed", ErrorCategory.InvalidArgument, resolvedComputerNames[i]); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); writer.Write(errorWriter); } } @@ -954,7 +941,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( @@ -1107,7 +1094,9 @@ private List CreateRunspacesForSSHHostParameterSet() host, this.KeyFilePath, port, - Subsystem); + Subsystem, + ConnectingTimeout, + Options); var typeTable = TypeTable.LoadDefaultTypeFiles(); string rsName = GetRunspaceName(index, out int rsIdUnused); index++; @@ -1133,7 +1122,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++; @@ -1262,10 +1253,7 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) ErrorRecord errorRecord = new ErrorRecord(e, "CreateRemoteRunspaceFailed", ErrorCategory.InvalidArgument, uri); - Action errorWriter = delegate (Cmdlet cmdlet) - { - cmdlet.WriteError(errorRecord); - }; + Action errorWriter = (Cmdlet cmdlet) => cmdlet.WriteError(errorRecord); writer.Write(errorWriter); } @@ -1274,10 +1262,10 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) #region Private Members private ThrottleManager _throttleManager = new ThrottleManager(); - private ObjectStream _stream = new ObjectStream(); + private readonly ObjectStream _stream = new ObjectStream(); // event that signals that all operations are // complete (including closing if any) - private ManualResetEvent _operationsComplete = new ManualResetEvent(true); + private readonly ManualResetEvent _operationsComplete = new ManualResetEvent(true); // the initial state is true because when no // operations actually take place as in case of a // parameter binding exception, then Dispose is @@ -1286,13 +1274,13 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) // BeginProcessing() // list of runspaces to dispose - private List _toDispose = new List(); + private readonly List _toDispose = new List(); // List of runspace connect operations. Need to keep for cleanup. - private Collection> _allOperations = new Collection>(); + private readonly Collection> _allOperations = new Collection>(); // Default FQEID. - private string _defaultFQEID = "PSSessionOpenFailed"; + private readonly string _defaultFQEID = "PSSessionOpenFailed"; #endregion Private Members } @@ -1310,7 +1298,7 @@ internal class OpenRunspaceOperation : IThrottleOperation, IDisposable private bool _startComplete; private bool _stopComplete; - private object _syncObject = new object(); + private readonly object _syncObject = new object(); internal RemoteRunspace OperatedRunspace { get; } @@ -1375,7 +1363,7 @@ internal override void StopOperation() // b) To ensure all callbacks are fired by manually invoking callbacks and handling // any exceptions thrown on this thread. (ThrottleManager will not respond if it doesn't // get a start/stop complete callback). - private List> _internalCallbacks = new List>(); + private readonly List> _internalCallbacks = new List>(); internal override event EventHandler OperationComplete { @@ -1406,7 +1394,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 ad233ccb481..3e2603a36fe 100644 --- a/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs +++ b/src/System.Management.Automation/engine/remoting/commands/remotingcommandutil.cs @@ -87,13 +87,13 @@ internal static void CheckRemotingCmdletPrerequisites() return; #else bool notSupported = true; - string WSManKeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\WSMAN\\"; + const string WSManKeyPath = "Software\\Microsoft\\Windows\\CurrentVersion\\WSMAN\\"; CheckHostRemotingPrerequisites(); 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,61 +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/AsyncObject.cs b/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs index a68a6f78869..88a329cb6b6 100644 --- a/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs +++ b/src/System.Management.Automation/engine/remoting/common/AsyncObject.cs @@ -22,7 +22,7 @@ internal class AsyncObject where T : class /// /// Value was set. /// - private ManualResetEvent _valueWasSet; + private readonly ManualResetEvent _valueWasSet; /// /// Value. @@ -32,7 +32,7 @@ internal T Value get { bool result = _valueWasSet.WaitOne(); - if (result == false) + if (!result) { _value = null; } diff --git a/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs b/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs index 035d39ac84e..9dc073e6616 100644 --- a/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs +++ b/src/System.Management.Automation/engine/remoting/common/DispatchTable.cs @@ -33,7 +33,7 @@ internal class DispatchTable where T : class /// /// Response async objects. /// - private Dictionary> _responseAsyncObjects = new Dictionary>(); + private readonly Dictionary> _responseAsyncObjects = new Dictionary>(); /// /// Next call id. diff --git a/src/System.Management.Automation/engine/remoting/common/Indexer.cs b/src/System.Management.Automation/engine/remoting/common/Indexer.cs index 0f6b70482c8..019eb9dfce4 100644 --- a/src/System.Management.Automation/engine/remoting/common/Indexer.cs +++ b/src/System.Management.Automation/engine/remoting/common/Indexer.cs @@ -16,12 +16,12 @@ internal class Indexer : IEnumerable, IEnumerator /// /// Current. /// - private int[] _current; + private readonly int[] _current; /// /// Lengths. /// - private int[] _lengths; + private readonly int[] _lengths; /// /// Current. @@ -49,7 +49,7 @@ internal Indexer(int[] lengths) /// Check lengths non negative. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - private bool CheckLengthsNonNegative(int[] lengths) + private static bool CheckLengthsNonNegative(int[] lengths) { for (int i = 0; i < lengths.Length; ++i) { diff --git a/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs b/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs index 25534b59a30..a5ae0dab19c 100644 --- a/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs +++ b/src/System.Management.Automation/engine/remoting/common/ObjectRef.cs @@ -20,7 +20,7 @@ internal class ObjectRef where T : class /// /// Old value. /// - private T _oldValue; + private readonly T _oldValue; /// /// Old value. diff --git a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs index 5eb9c5fc338..989ad33e987 100644 --- a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs +++ b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs @@ -158,11 +158,17 @@ 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, ExperimentalFeature_ReadConfig_Error = 0x3002, + // Windows Diagnostics And Usage Data Settings + Telemetry_Setting_Error = 0x3011, + // Scheduled Jobs ScheduledJob_Start = 0xD001, ScheduledJob_Complete = 0xD002, @@ -237,9 +243,13 @@ internal enum PSTask : int ProviderStop = 0x69, ExecutePipeline = 0x6A, ExperimentalFeature = 0x6B, + Telemetry = 0x6C, 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 730a4aa9a2d..8d53adf0ca8 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -7,17 +7,19 @@ using System.Net.Sockets; using System.Text; using System.Threading; +using System.Buffers; using Dbg = System.Diagnostics.Debug; +using SMA = System.Management.Automation; namespace System.Management.Automation.Remoting { - [SerializableAttribute] + [Serializable] internal class HyperVSocketEndPoint : EndPoint { #region Members - private System.Net.Sockets.AddressFamily _addressFamily; + private readonly System.Net.Sockets.AddressFamily _addressFamily; private Guid _vmId; private Guid _serviceId; @@ -53,7 +55,7 @@ public Guid ServiceId { get { return _serviceId; } - set { _vmId = value; } + set { _serviceId = value; } } #endregion @@ -138,7 +140,11 @@ internal sealed class RemoteSessionHyperVSocketServer : IDisposable #region Members private readonly object _syncObject; - private PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); + private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); + + // This is to prevent persistent replay attacks. + // it is not meant to ensure all replay attacks are impossible. + private const int MAX_TOKEN_LIFE_MINUTES = 10; #endregion @@ -175,64 +181,74 @@ internal sealed class RemoteSessionHyperVSocketServer : IDisposable public RemoteSessionHyperVSocketServer(bool LoopbackMode) { - // TODO: uncomment below code when .NET supports Hyper-V socket duplication - /* - NamedPipeClientStream clientPipeStream; - byte[] buffer = new byte[1000]; - int bytesRead; - */ _syncObject = new object(); Exception ex = null; try { - // TODO: uncomment below code when .NET supports Hyper-V socket duplication - /* - if (!LoopbackMode) - { - // - // Create named pipe client. - // - using (clientPipeStream = new NamedPipeClientStream(".", - "PS_VMSession", - PipeDirection.InOut, - PipeOptions.None, - TokenImpersonationLevel.None)) - { - // - // Connect to named pipe server. - // - clientPipeStream.Connect(10*1000); - - // - // Read LPWSAPROTOCOL_INFO. - // - bytesRead = clientPipeStream.Read(buffer, 0, 1000); - } - } + Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2 + Guid loopbackId = new Guid("e0e16197-dd56-4a10-9195-5ee7a155a838"); // HV_GUID_LOOPBACK + Guid parentId = new Guid("a42e7cda-d03f-480c-9cc2-a4de20abb878"); // HV_GUID_PARENT + Guid vmId = LoopbackMode ? loopbackId : parentId; + HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId); + + Socket listenSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + listenSocket.Bind(endpoint); + + listenSocket.Listen(1); + HyperVSocket = listenSocket.Accept(); + + Stream = new NetworkStream(HyperVSocket, true); + + // Create reader/writer streams. + TextReader = new StreamReader(Stream); + TextWriter = new StreamWriter(Stream); + TextWriter.AutoFlush = true; // - // Create duplicate socket. + // listenSocket is not closed when it goes out of scope here. Sometimes it is + // closed later in this thread, while other times it is not closed at all. This will + // cause problem when we set up a second PowerShell Direct session. Let's + // explicitly close listenSocket here for safe. // - byte[] protocolInfo = new byte[bytesRead]; - Array.Copy(buffer, protocolInfo, bytesRead); + if (listenSocket != null) + { + try { listenSocket.Dispose(); } + catch (ObjectDisposedException) { } + } + } + catch (Exception e) + { + ex = e; + } - SocketInformation sockInfo = new SocketInformation(); - sockInfo.ProtocolInformation = protocolInfo; - sockInfo.Options = SocketInformationOptions.Connected; + if (ex != null) + { + Dbg.Fail("Unexpected error in RemoteSessionHyperVSocketServer."); - socket = new Socket(sockInfo); - if (socket == null) - { - Dbg.Assert(false, "Unexpected error in RemoteSessionHyperVSocketServer."); + // Unexpected error. + string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty; + _tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, + "Unexpected error in constructor: {0}", errorMessage); - tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, - "Unexpected error in constructor: {0}", "socket duplication failure"); - } - */ + throw new PSInvalidOperationException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure), + ex, + nameof(PSRemotingErrorId.RemoteSessionHyperVSocketServerConstructorFailure), + ErrorCategory.InvalidOperation, + null); + } + } + + public RemoteSessionHyperVSocketServer(bool LoopbackMode, string token, DateTimeOffset tokenCreationTime) + { + _syncObject = new object(); + + Exception ex = null; - // TODO: remove below 6 lines of code when .NET supports Hyper-V socket duplication + try + { Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2 HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, Guid.Empty, serviceId); @@ -242,6 +258,8 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) listenSocket.Listen(1); HyperVSocket = listenSocket.Accept(); + ValidateToken(HyperVSocket, token, tokenCreationTime, MAX_TOKEN_LIFE_MINUTES * 60); + Stream = new NetworkStream(HyperVSocket, true); // Create reader/writer streams. @@ -257,8 +275,13 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) // if (listenSocket != null) { - try { listenSocket.Dispose(); } - catch (ObjectDisposedException) { } + try + { + listenSocket.Dispose(); + } + catch (ObjectDisposedException) + { + } } } catch (Exception e) @@ -272,8 +295,12 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) // Unexpected error. string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty; - _tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, - "Unexpected error in constructor: {0}", errorMessage); + _tracer.WriteMessage( + "RemoteSessionHyperVSocketServer", + "RemoteSessionHyperVSocketServer", + Guid.Empty, + "Unexpected error in constructor: {0}", + errorMessage); throw new PSInvalidOperationException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure), @@ -283,7 +310,6 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) null); } } - #endregion #region IDisposable @@ -295,7 +321,10 @@ public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } IsDisposed = true; } @@ -330,6 +359,107 @@ public void Dispose() } #endregion + + /// + /// Validates the token received from the client over the HyperVSocket. + /// Throws PSDirectException if the token is invalid or not received in time. + /// + /// The connected HyperVSocket. + /// The expected token string. + /// The creation time of the token. + /// The maximum lifetime of the token in seconds. + internal static void ValidateToken(Socket socket, string token, DateTimeOffset tokenCreationTime, int maxTokenLifeSeconds) + { + TimeSpan timeout = TimeSpan.FromSeconds(maxTokenLifeSeconds); + DateTimeOffset timeoutExpiry = tokenCreationTime.Add(timeout); + DateTimeOffset now = DateTimeOffset.UtcNow; + + // Calculate remaining time and create cancellation token + TimeSpan remainingTime = timeoutExpiry - now; + + // Check if the token has already expired + if (remainingTime <= TimeSpan.Zero) + { + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential, "Token has expired")); + } + + // Create a cancellation token that will be cancelled when the timeout expires + using var cancellationTokenSource = new CancellationTokenSource(remainingTime); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + // Set socket timeout for receive operations to prevent indefinite blocking + int timeoutMs = (int)remainingTime.TotalMilliseconds; + socket.ReceiveTimeout = timeoutMs; + socket.SendTimeout = timeoutMs; + + // Check for cancellation before starting validation + cancellationToken.ThrowIfCancellationRequested(); + + // We should move to this pattern and + // in the tests I found I needed to get a bigger buffer than the token length + // and test length of the received data similar to this pattern. + string responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length + 4); + if (string.IsNullOrEmpty(responseString) || responseString.Length != RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Request: " + responseString)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + socket.Send(Encoding.UTF8.GetBytes(RemoteSessionHyperVSocketClient.CLIENT_VERSION)); + responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.CLIENT_VERSION.Length + 4); + + // In the future we may need to handle different versions, differently. + // For now, we are just checking that we exchanged versions correctly. + if (string.IsNullOrEmpty(responseString) || !responseString.StartsWith(RemoteSessionHyperVSocketClient.VERSION_PREFIX, StringComparison.Ordinal)) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Response: " + responseString)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + socket.Send("PASS"u8); + + // The client should send the token in the format TOKEN + // the token should be up to 256 bits, which is less than 50 characters. + // I'll double that to 100 characters to be safe, plus the "TOKEN " prefix. + // So we expect a response of length 6 + 100 = 106 characters. + responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, 110); + + // Final check if we got the token before the timeout + cancellationToken.ThrowIfCancellationRequested(); + + ReadOnlySpan responseBytes = Encoding.UTF8.GetBytes(responseString); + string responseToken = RemoteSessionHyperVSocketClient.ExtractToken(responseBytes); + + if (responseToken == null) + { + socket.Send("FAIL"u8); + // If the response is not in the expected format, we throw an exception. + // This is a failure to authenticate the client. + // don't send this response for risk of information disclosure. + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Token Response")); + } + + if (!string.Equals(responseToken, token, StringComparison.Ordinal)) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + // Acknowledge the token is valid with "PASS". + socket.Send("PASS"u8); + + socket.ReceiveTimeout = 0; // Disable the timeout after successful validation + socket.SendTimeout = 0; + } } internal sealed class RemoteSessionHyperVSocketClient : IDisposable @@ -337,9 +467,17 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable #region Members private readonly object _syncObject; - private PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); - private static ManualResetEvent s_connectDone = + #region tracer + /// + /// An instance of the PSTraceSource class used for trace output. + /// + [SMA.TraceSource("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation"); + + #endregion tracer + + private static readonly ManualResetEvent s_connectDone = new ManualResetEvent(false); #endregion @@ -351,6 +489,14 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable #endregion + #region version constants + + internal const string VERSION_REQUEST = "VERSION"; + internal const string CLIENT_VERSION = "VERSION_2"; + internal const string VERSION_PREFIX = "VERSION_"; + + #endregion + #region Properties /// @@ -361,7 +507,7 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable /// /// Returns the Hyper-V socket object. /// - public Socket HyperVSocket { get; } + public Socket HyperVSocket { get; private set; } /// /// Returns the network stream object. @@ -378,6 +524,37 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable /// public StreamWriter TextWriter { get; private set; } + /// + /// True if the client is a Hyper-V container. + /// + public bool IsContainer { get; } + + /// + /// True if the client is using backwards compatible mode. + /// This is used to determine if the client should use + /// the backwards compatible or not. + /// In modern mode, the vmicvmsession service will + /// hand off the socket to the PowerShell process + /// inside the VM automatically. + /// In backwards compatible mode, the vmicvmsession + /// service create a new socket to the PowerShell process + /// inside the VM. + /// + public bool UseBackwardsCompatibleMode { get; private set; } + + /// + /// The authentication token used for the session. + /// This token is provided by the broker and provided to the server to authenticate the server session. + /// This protocol uses two connections: + /// 1. The first is to the broker or vmicvmsession service to exchange credentials and configuration. + /// The broker will respond with an authentication token. The broker also launches a PowerShell + /// server process with the authentication token. + /// 2. The second is to the server process, that was launched by the broker, + /// inside the VM, which uses the authentication token to verify that the client is the same client + /// that connected to the broker. + /// + public string AuthenticationToken { get; private set; } + /// /// Returns true if object is currently disposed. /// @@ -390,7 +567,9 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable internal RemoteSessionHyperVSocketClient( Guid vmId, bool isFirstConnection, - bool isContainer = false) + bool useBackwardsCompatibleMode = false, + bool isContainer = false, + string authenticationToken = null) { Guid serviceId; @@ -409,28 +588,16 @@ internal RemoteSessionHyperVSocketClient( EndPoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId); - HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + IsContainer = isContainer; - // - // We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container. - // Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host - // - if (isContainer) - { - var value = new byte[sizeof(uint)]; - value[0] = 1; + UseBackwardsCompatibleMode = useBackwardsCompatibleMode; - try - { - HyperVSocket.SetSocketOption((System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW, - (System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU, - (byte[])value); - } - catch - { - throw new PSDirectException( - PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure)); - } + if (!isFirstConnection && !useBackwardsCompatibleMode && !string.IsNullOrEmpty(authenticationToken)) + { + // If this is not the first connection and we are using backwards compatible mode, + // we should not set the authentication token here. + // The authentication token will be set during the Connect method. + AuthenticationToken = authenticationToken; } } @@ -445,7 +612,10 @@ public void Dispose() { lock (_syncObject) { - if (IsDisposed) { return; } + if (IsDisposed) + { + return; + } IsDisposed = true; } @@ -483,6 +653,81 @@ public void Dispose() #region Public Methods + private void ShutdownSocket() + { + if (HyperVSocket != null) + { + // Ensure the socket is disposed properly. + try + { + s_tracer.WriteLine("ShutdownSocket: Disposing of the HyperVSocket."); + HyperVSocket.Dispose(); + } + catch (Exception ex) + { + s_tracer.WriteLine("ShutdownSocket: Exception while disposing the socket: {0}", ex.Message); + } + } + + // Dispose of the existing stream if it exists. + if (Stream != null) + { + try + { + Stream.Dispose(); + } + catch (Exception ex) + { + s_tracer.WriteLine("ShutdownSocket: Exception while disposing the stream: {0}", ex.Message); + } + } + } + + /// + /// Recreates the HyperVSocket and connects it to the endpoint, updating the Stream if successful. + /// + private bool ConnectSocket() + { + HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + + // + // We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container. + // Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host + // + if (IsContainer) + { + var value = new byte[sizeof(uint)]; + value[0] = 1; + + try + { + HyperVSocket.SetSocketOption( + (System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW, + (System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU, + value); + } + catch + { + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure)); + } + } + + s_tracer.WriteLine("Connect: Client connecting, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer); + HyperVSocket.Connect(EndPoint); + + // Check if the socket is connected. + // If it is connected, create a NetworkStream. + if (HyperVSocket.Connected) + { + s_tracer.WriteLine("Connect: Client connected, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer); + Stream = new NetworkStream(HyperVSocket, true); + return true; + } + + return false; + } + /// /// Connect to Hyper-V socket server. This is a blocking call until a /// connection occurs or the timeout time has elapsed. @@ -510,100 +755,51 @@ public bool Connect( } } - HyperVSocket.Connect(EndPoint); - - if (HyperVSocket.Connected) + if (ConnectSocket()) { - _tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty, - "Client connected."); - - Stream = new NetworkStream(HyperVSocket, true); - if (isFirstConnection) { - if (string.IsNullOrEmpty(networkCredential.Domain)) + var exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode); + if (!exchangeResult.success) { - networkCredential.Domain = "localhost"; - } + // We will not block here for a container because a container does not have a broker. + if (IsRequirePsDirectAuthenticationEnabled(@"SOFTWARE\\Microsoft\\PowerShell", Microsoft.Win32.RegistryHive.LocalMachine)) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: RequirePsDirectAuthentication is enabled, requiring latest transport version."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVNegotiationFailed)); + } - bool emptyPassword = string.IsNullOrEmpty(networkCredential.Password); - bool emptyConfiguration = string.IsNullOrEmpty(configurationName); - - byte[] domain = Encoding.Unicode.GetBytes(networkCredential.Domain); - byte[] userName = Encoding.Unicode.GetBytes(networkCredential.UserName); - byte[] password = Encoding.Unicode.GetBytes(networkCredential.Password); - byte[] response = new byte[4]; // either "PASS" or "FAIL" - string responseString; - - // - // Send credential to VM so that PowerShell process inside VM can be - // created under the correct security context. - // - HyperVSocket.Send(domain); - HyperVSocket.Receive(response); - - HyperVSocket.Send(userName); - HyperVSocket.Receive(response); - - // - // We cannot simply send password because if it is empty, - // the vmicvmsession service in VM will block in recv method. - // - if (emptyPassword) - { - HyperVSocket.Send(Encoding.ASCII.GetBytes("EMPTYPW")); - HyperVSocket.Receive(response); - responseString = Encoding.ASCII.GetString(response); + this.UseBackwardsCompatibleMode = true; + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Using backwards compatible mode."); + + // If the first connection fails in modern mode, fall back to backwards compatible mode. + ShutdownSocket(); // will terminate the broker + ConnectSocket(); // restart the broker + exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode); + if (!exchangeResult.success) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Failed to exchange credentials and configuration in backwards compatible mode."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential")); + } } else { - HyperVSocket.Send(Encoding.ASCII.GetBytes("NONEMPTYPW")); - HyperVSocket.Receive(response); - - HyperVSocket.Send(password); - HyperVSocket.Receive(response); - responseString = Encoding.ASCII.GetString(response); + this.AuthenticationToken = exchangeResult.authenticationToken; } + } - // - // There are 3 cases for the responseString received above. - // - "FAIL": credential is invalid - // - "PASS": credential is valid, but PowerShell Direct in VM does not support configuration (Server 2016 TP4 and before) - // - "CONF": credential is valid, and PowerShell Direct in VM supports configuration (Server 2016 TP5 and later) - // - - // - // Credential is invalid. - // - if (string.Equals(responseString, "FAIL", StringComparison.Ordinal)) + if (!isFirstConnection) + { + if (!this.UseBackwardsCompatibleMode) { - HyperVSocket.Send(response); - - throw new PSDirectException( - PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); - } - - // - // If PowerShell Direct in VM supports configuration, send configuration name. - // - if (string.Equals(responseString, "CONF", StringComparison.Ordinal)) - { - if (emptyConfiguration) - { - HyperVSocket.Send(Encoding.ASCII.GetBytes("EMPTYCF")); - } - else - { - HyperVSocket.Send(Encoding.ASCII.GetBytes("NONEMPTYCF")); - HyperVSocket.Receive(response); - - byte[] configName = Encoding.Unicode.GetBytes(configurationName); - HyperVSocket.Send(configName); - } + s_tracer.WriteLine("Connect-Server: Performing transport version and token exchange for Hyper-V socket. isFirstConnection: {0}, UseBackwardsCompatibleMode: {1}", isFirstConnection, this.UseBackwardsCompatibleMode); + RemoteSessionHyperVSocketClient.PerformTransportVersionAndTokenExchange(HyperVSocket, this.AuthenticationToken); } else { - HyperVSocket.Send(response); + s_tracer.WriteLine("Connect-Server: Skipping transport version and token exchange for backwards compatible mode."); } } @@ -615,8 +811,7 @@ public bool Connect( } else { - _tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty, - "Client unable to connect."); + s_tracer.WriteLine("Connect: Client unable to connect."); result = false; } @@ -624,12 +819,341 @@ public bool Connect( return result; } + /// + /// Performs the transport version and token exchange sequence for the Hyper-V socket connection. + /// Throws PSDirectException on failure. + /// + /// The socket to use for communication. + /// The authentication token to send. + public static void PerformTransportVersionAndTokenExchange(Socket socket, string authenticationToken) + { + if (string.IsNullOrEmpty(authenticationToken)) + { + s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Authentication token is null or empty. Aborting transport version and token exchange."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + socket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST)); + string responseStr = ReceiveResponse(socket, 16); + + // Check if the response starts with the expected version prefix. + // We will rely on the broker to determine if the two can communicate. + // At least, for now. + if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal)) + { + s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Server responded with an invalid response of {0}. Notifying the transport manager to downgrade if allowed.", responseStr); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion")); + } + + socket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION)); + string response = ReceiveResponse(socket, 4); // either "PASS" or "FAIL" + + if (!string.Equals(response, "PASS", StringComparison.Ordinal)) + { + s_tracer.WriteLine( + "PerformTransportVersionAndTokenExchange: Transport version negotiation with server failed. Response: {0}", response); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion")); + } + + byte[] tokenBytes = Encoding.UTF8.GetBytes("TOKEN " + authenticationToken); + socket.Send(tokenBytes); + + // This is the opportunity for the server to tell the client to go away. + string tokenResponse = ReceiveResponse(socket, 256); // either "PASS" or "FAIL", but get a little more buffer to allow for better error in the future + if (!string.Equals(tokenResponse, "PASS", StringComparison.Ordinal)) + { + s_tracer.WriteLine( + "PerformTransportVersionAndTokenExchange: Server Authentication Token exchange failed. Response: {0}", tokenResponse); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + } + + /// + /// Checks if the registry key RequirePsDirectAuthentication is set to 1. + /// Returns true if fallback should be aborted. + /// Uses the 64-bit registry view on 64-bit systems to ensure consistent behavior regardless of process architecture. + /// On 32-bit systems, uses the default registry view since there is no WOW64 redirection. + /// + internal static bool IsRequirePsDirectAuthenticationEnabled(string keyPath, Microsoft.Win32.RegistryHive registryHive) + { + const string regValueName = "RequirePsDirectAuthentication"; + + try + { + Microsoft.Win32.RegistryView registryView = Environment.Is64BitOperatingSystem + ? Microsoft.Win32.RegistryView.Registry64 + : Microsoft.Win32.RegistryView.Default; + + using (Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey( + registryHive, + registryView)) + { + using (Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(keyPath)) + { + if (key != null) + { + var value = key.GetValue(regValueName); + if (value is int intValue && intValue != 0) + { + return true; + } + } + + return false; + } + } + } + catch (Exception regEx) + { + s_tracer.WriteLine("IsRequirePsDirectAuthenticationEnabled: Exception while checking registry key: {0}", regEx.Message); + return false; // If we cannot read the registry, assume the feature is not enabled. + } + } + + /// + /// Handles credential and configuration exchange with the VM for the first connection. + /// + public static (bool success, string authenticationToken) ExchangeCredentialsAndConfiguration(NetworkCredential networkCredential, string configurationName, Socket HyperVSocket, bool useBackwardsCompatibleMode) + { + // Encoding for the Hyper-V socket communication + // To send the domain, username, password, and configuration name, use UTF-16 (Encoding.Unicode) + // All other sends use UTF-8 (Encoding.UTF8) + // Receiving uses ASCII encoding + // NOT CONFUSING AT ALL + + if (!useBackwardsCompatibleMode) + { + HyperVSocket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST)); + // vmicvmsession service in VM will respond with "VERSION_2" or newer + // Version 1 protocol will respond with "PASS" or "FAIL" + // Receive the response and check for VERSION_2 or newer + string responseStr = ReceiveResponse(HyperVSocket, 16); + if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal)) + { + s_tracer.WriteLine("When asking for version the server responded with an invalid response of {0}.", responseStr); + s_tracer.WriteLine("Session is invalid, continuing session with a fake user to close the session with the broker for stability."); + // If not the new protocol, finish the conversation + // Send a fake user + // Use ? <> that are illegal in user names so no one can create the user + string probeUserName = "?"; // must be less than or equal to 20 characters for Windows Server 2016 + s_tracer.WriteLine("probeUserName (static): length: {0}", probeUserName.Length); + SendUserData(probeUserName, HyperVSocket); + responseStr = ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL" + s_tracer.WriteLine("When sending user {0}.", responseStr); + + // Send that the password is empty + HyperVSocket.Send("EMPTYPW"u8); + responseStr = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" + s_tracer.WriteLine("When sending EMPTYPW: {0}.", responseStr); // server responds with FAIL so we respond with FAIL and the conversation is done + HyperVSocket.Send("FAIL"u8); + + s_tracer.WriteLine("Notifying the transport manager to downgrade if allowed."); + // end new code + return (false, null); + } + + HyperVSocket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION)); + ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL" + } + + if (string.IsNullOrEmpty(networkCredential.Domain)) + { + networkCredential.Domain = "localhost"; + } + + System.Security.SecureString securePassword = networkCredential.SecurePassword; + int passwordLength = securePassword.Length; + bool emptyPassword = (passwordLength <= 0); + bool emptyConfiguration = string.IsNullOrEmpty(configurationName); + + string responseString; + + // Send credential to VM so that PowerShell process inside VM can be + // created under the correct security context. + SendUserData(networkCredential.Domain, HyperVSocket); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + SendUserData(networkCredential.UserName, HyperVSocket); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + // We cannot simply send password because if it is empty, + // the vmicvmsession service in VM will block in recv method. + if (emptyPassword) + { + HyperVSocket.Send("EMPTYPW"u8); + responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode) + } + else + { + HyperVSocket.Send("NONEMPTYPW"u8); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + // Get the password bytes from the SecureString, send them, and then zero out the byte array. + byte[] passwordBytes = Microsoft.PowerShell.SecureStringHelper.GetData(securePassword); + try + { + HyperVSocket.Send(passwordBytes); + } + finally + { + // Zero out the byte array for security + Array.Clear(passwordBytes); + } + + responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode) + } + + // Check for invalid response from server + if (!string.Equals(responseString, "FAIL", StringComparison.Ordinal) && + !string.Equals(responseString, "PASS", StringComparison.Ordinal) && + !string.Equals(responseString, "CONF", StringComparison.Ordinal)) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with an invalid response of {0} for credentials.", responseString); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential")); + } + + // Credential is invalid. + if (string.Equals(responseString, "FAIL", StringComparison.Ordinal)) + { + HyperVSocket.Send("FAIL"u8); + // should we be doing this? Disabling the test for now + // HyperVSocket.Shutdown(SocketShutdown.Both); + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with FAIL for credentials."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + // If PowerShell Direct in VM supports configuration, send configuration name. + if (string.Equals(responseString, "CONF", StringComparison.Ordinal)) + { + if (emptyConfiguration) + { + HyperVSocket.Send("EMPTYCF"u8); + } + else + { + HyperVSocket.Send("NONEMPTYCF"u8); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + SendUserData(configurationName, HyperVSocket); + } + } + else + { + HyperVSocket.Send("PASS"u8); + } + + if (!useBackwardsCompatibleMode) + { + // Receive the token from the server + // Getting 1024 bytes because it is well above the expected token size + // The expected size at the time of writing this would be about 50 based64 characters, + // plus the 6 characters for the "TOKEN " prefix. + // The 50 character size is designed to last 10 years of cryptographic changes. + // Since the broker completely controls the cryptographic portion here, + // allowing a significant larger size, allows the broker to make almost arbitrary changes, + // without breaking the client. + string token = ReceiveResponse(HyperVSocket, 1024); // either "PASS" or "FAIL" + + ReadOnlySpan tokenResponseBytes = Encoding.UTF8.GetBytes(token); + string extractedToken = ExtractToken(tokenResponseBytes); + + if (extractedToken == null) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server did not respond with a valid token. Response: {0}", token); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Token " + token)); + } + + token = extractedToken; + + HyperVSocket.Send("PASS"u8); // acknowledge the token + return (true, token); + } + + return (true, null); + } + public void Close() { Stream.Dispose(); HyperVSocket.Dispose(); } + /// + /// Receives a response from the socket and decodes it. + /// + /// The socket to receive from. + /// The size of the buffer to use for receiving data. + /// The decoded response string. + internal static string ReceiveResponse(Socket socket, int bufferSize) + { + System.Buffers.ArrayPool pool = System.Buffers.ArrayPool.Shared; + byte[] responseBuffer = pool.Rent(bufferSize); + int bytesReceived = 0; + try + { + bytesReceived = socket.Receive(responseBuffer); + if (bytesReceived == 0) + { + return null; + } + + string response = Encoding.ASCII.GetString(responseBuffer, 0, bytesReceived); + + // Handle null terminators and log if found + if (response.EndsWith('\0')) + { + int originalLength = response.Length; + response = response.TrimEnd('\0'); + // Cannot log actual response, because we don't know if it is sensitive + s_tracer.WriteLine( + "ReceiveResponse: Removed null terminator(s). Original length: {0}, New length: {1}", + originalLength, + response.Length); + } + + return response; + } + finally + { + pool.Return(responseBuffer); + } + } + + internal static string ExtractToken(ReadOnlySpan tokenResponse) + { + string token = Encoding.UTF8.GetString(tokenResponse); + + if (token == null || !token.StartsWith("TOKEN ", StringComparison.Ordinal)) + { + return null; // caller method will write trace (and determine when to expose token info as appropriate) + } + + token = token.Substring(6).Trim(); // remove "TOKEN " prefix + + if (token.Length == 0) + { + return null; + } + + return token; + } + + /// + /// Sends user data (domain, username, etc.) over the HyperVSocket using Unicode encoding. + /// + private static void SendUserData(string data, Socket socket) + { + // this encodes the data in UTF-16 (Unicode) + byte[] buffer = Encoding.Unicode.GetBytes(data); + socket.Send(buffer); + } #endregion } } diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 3a75567c10f..fc5226a007e 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -12,7 +12,6 @@ using System.Security.AccessControl; using System.Security.Principal; using System.Threading; -using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -110,7 +109,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 +189,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 +244,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 } @@ -301,20 +258,12 @@ internal sealed class ListenerEndedEventArgs : EventArgs /// Exception reason for listener end event. Can be null /// which indicates listener thread end is not due to an error. /// - public Exception Reason - { - private set; - get; - } + public Exception Reason { get; } /// /// True if listener should be restarted after ending. /// - public bool RestartListener - { - private set; - get; - } + public bool RestartListener { get; } #endregion @@ -349,7 +298,7 @@ public sealed class RemoteSessionNamedPipeServer : IDisposable #region Members private readonly object _syncObject; - private PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); + private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); private const string _threadName = "IPC Listener Thread"; private const int _namedPipeBufferSizeForRemoting = 32768; @@ -357,7 +306,7 @@ public sealed class RemoteSessionNamedPipeServer : IDisposable private const int _maxPipePathLengthMacOS = 104; // Singleton server. - private static object s_syncObject; + private static readonly object s_syncObject; internal static RemoteSessionNamedPipeServer IPCNamedPipeServer; internal static bool IPCNamedPipeServerEnabled; @@ -474,7 +423,7 @@ internal RemoteSessionNamedPipeServer( /// Named pipe core name. /// /// NamedPipeServerStream. - private NamedPipeServerStream CreateNamedPipe( + private static NamedPipeServerStream CreateNamedPipe( string serverName, string namespaceName, string coreName, @@ -504,7 +453,7 @@ private NamedPipeServerStream CreateNamedPipe( SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe( fullPipeName, NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED, - NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE, + NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_REJECT_REMOTE_CLIENTS, 1, _namedPipeBufferSizeForRemoting, _namedPipeBufferSizeForRemoting, @@ -512,10 +461,7 @@ private NamedPipeServerStream CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -553,13 +499,14 @@ static RemoteSessionNamedPipeServer() { s_syncObject = new object(); - // All PowerShell instances will start with the named pipe - // and listener created and running. - IPCNamedPipeServerEnabled = true; - - CreateIPCNamedPipeServerSingleton(); + // Unless opt-out, all PowerShell instances will start with the named-pipe listener created and running. + IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_DIAGNOSTICS_OPTOUT", defaultValue: false); - CreateProcessExitHandler(); + if (IPCNamedPipeServerEnabled) + { + CreateIPCNamedPipeServerSingleton(); + CreateProcessExitHandler(); + } } #endregion @@ -752,7 +699,7 @@ private void WaitForConnection() [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")] private void ProcessListeningThread(object state) { - string processId = System.Diagnostics.Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture); + string processId = Environment.ProcessId.ToString(CultureInfo.InvariantCulture); string appDomainName = NamedPipeUtils.GetCurrentAppDomainName(); // Logging. @@ -908,10 +855,7 @@ internal static void RunServerMode(string configurationName) ManualResetEventSlim clientConnectionEnded = new ManualResetEventSlim(false); IPCNamedPipeServer.ListenerEnded -= OnIPCNamedPipeServerEnded; - IPCNamedPipeServer.ListenerEnded += (sender, e) => - { - clientConnectionEnded.Set(); - }; + IPCNamedPipeServer.ListenerEnded += (sender, e) => clientConnectionEnded.Set(); // Wait for server to service a single client connection. clientConnectionEnded.Wait(); @@ -1018,9 +962,7 @@ internal class NamedPipeClientBase : IDisposable #region Members private NamedPipeClientStream _clientPipeStream; - private PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); - - protected string _pipeName; + private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); #endregion @@ -1041,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(); } @@ -1104,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; @@ -1152,9 +1099,8 @@ private RemoteSessionNamedPipeClient() /// /// Target process object for pipe. /// AppDomain name or null for default AppDomain. - public RemoteSessionNamedPipeClient( - System.Diagnostics.Process process, string appDomainName) : - this(NamedPipeUtils.CreateProcessPipeName(process, appDomainName)) + public RemoteSessionNamedPipeClient(System.Diagnostics.Process process, string appDomainName) + : this(NamedPipeUtils.CreateProcessPipeName(process, appDomainName)) { } /// @@ -1162,9 +1108,8 @@ public RemoteSessionNamedPipeClient( /// /// Target process Id for pipe. /// AppDomain name or null for default AppDomain. - public RemoteSessionNamedPipeClient( - int procId, string appDomainName) : - this(NamedPipeUtils.CreateProcessPipeName(procId, appDomainName)) + public RemoteSessionNamedPipeClient(int procId, string appDomainName) + : this(NamedPipeUtils.CreateProcessPipeName(procId, appDomainName)) { } /// @@ -1179,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. @@ -1202,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. @@ -1224,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. @@ -1233,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 { @@ -1288,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); } @@ -1302,33 +1250,34 @@ public ContainerSessionNamedPipeClient( /// protected override NamedPipeClientStream DoConnect(int timeout) { - // Create pipe flags. - uint pipeFlags = NamedPipeNative.FILE_FLAG_OVERLAPPED; - +#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( - _pipeName, - NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, - 0, - IntPtr.Zero, - NamedPipeNative.OPEN_EXISTING, - pipeFlags, - 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); @@ -1346,19 +1295,21 @@ 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, 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 7f46151a857..475dc705674 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; } @@ -208,7 +204,10 @@ public CultureInfo UICulture /// public int OpenTimeout { - get { return _openTimeout; } + get + { + return _openTimeout; + } set { @@ -226,7 +225,7 @@ public int OpenTimeout // The timer constructor will throw an exception // for any value greater than Int32.MaxValue // hence this is the maximum possible limit - _openTimeout = Int32.MaxValue; + _openTimeout = int.MaxValue; } } } @@ -271,7 +270,7 @@ public int OpenTimeout /// The maximum allowed idle timeout duration (in ms) that can be set on a Runspace. This is a read-only property /// that is set once the Runspace is successfully created and opened. /// - public int MaxIdleTimeout { get; internal set; } = Int32.MaxValue; + public int MaxIdleTimeout { get; internal set; } = int.MaxValue; /// /// Populates session options from a PSSessionOption instance. @@ -279,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) { @@ -322,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) @@ -340,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 @@ -598,7 +598,10 @@ public override AuthenticationMechanism AuthenticationMechanism /// public override string CertificateThumbprint { - get { return _thumbPrint; } + get + { + return _thumbPrint; + } set { @@ -642,7 +645,7 @@ public override string CertificateThumbprint public bool UseCompression { get; set; } = true; /// - /// If true then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server + /// If then Operating System won't load the user profile (i.e. registry keys under HKCU) on the remote server /// which can result in a faster session creation time. This option won't have any effect if the remote machine has /// already loaded the profile (i.e. in another session). /// @@ -675,7 +678,10 @@ public override string CertificateThumbprint /// public AuthenticationMechanism ProxyAuthentication { - get { return _proxyAuthentication; } + get + { + return _proxyAuthentication; + } set { @@ -702,7 +708,10 @@ public AuthenticationMechanism ProxyAuthentication /// public PSCredential ProxyCredential { - get { return _proxyCredential; } + get + { + return _proxyCredential; + } set { @@ -829,8 +838,21 @@ public WSManConnectionInfo(string scheme, string computerName, int port, string /// max server life timeout and open timeout are /// default in this case [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Scope = "member", Target = "System.Management.Automation.Runspaces.WSManConnectionInfo.#.ctor(System.String,System.String,System.Int32,System.String,System.String,System.Management.Automation.PSCredential)", MessageId = "4#")] - public WSManConnectionInfo(string scheme, string computerName, int port, string appName, string shellUri, PSCredential credential) : - this(scheme, computerName, port, appName, shellUri, credential, DefaultOpenTimeout) + public WSManConnectionInfo( + string scheme, + string computerName, + int port, + string appName, + string shellUri, + PSCredential credential) + : this( + scheme, + computerName, + port, + appName, + shellUri, + credential, + DefaultOpenTimeout) { } @@ -844,9 +866,20 @@ public WSManConnectionInfo(string scheme, string computerName, int port, string /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] - public WSManConnectionInfo(bool useSsl, string computerName, int port, string appName, string shellUri, - PSCredential credential) : - this(useSsl ? DefaultSslScheme : DefaultScheme, computerName, port, appName, shellUri, credential) + public WSManConnectionInfo( + bool useSsl, + string computerName, + int port, + string appName, + string shellUri, + PSCredential credential) + : this( + useSsl ? DefaultSslScheme : DefaultScheme, + computerName, + port, + appName, + shellUri, + credential) { } @@ -860,8 +893,22 @@ public WSManConnectionInfo(bool useSsl, string computerName, int port, string ap /// /// [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "4#")] - public WSManConnectionInfo(bool useSsl, string computerName, int port, string appName, string shellUri, PSCredential credential, int openTimeout) : - this(useSsl ? DefaultSslScheme : DefaultScheme, computerName, port, appName, shellUri, credential, openTimeout) + public WSManConnectionInfo( + bool useSsl, + string computerName, + int port, + string appName, + string shellUri, + PSCredential credential, + int openTimeout) + : this( + useSsl ? DefaultSslScheme : DefaultScheme, + computerName, + port, + appName, + shellUri, + credential, + openTimeout) { } @@ -973,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)) { @@ -1012,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(); } @@ -1083,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, @@ -1096,7 +1147,7 @@ internal override BaseClientSessionTransportManager CreateClientSessionTransport #region Private Methods - private string ResolveShellUri(string shell) + private static string ResolveShellUri(string shell) { string resolvedShellUri = shell; if (string.IsNullOrEmpty(resolvedShellUri)) @@ -1104,10 +1155,9 @@ private string ResolveShellUri(string shell) resolvedShellUri = DefaultShellUri; } - if (resolvedShellUri.IndexOf( - System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase) == -1) + if (!resolvedShellUri.Contains(WSManNativeApi.ResourceURIPrefix, StringComparison.OrdinalIgnoreCase)) { - resolvedShellUri = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + resolvedShellUri; + resolvedShellUri = WSManNativeApi.ResourceURIPrefix + resolvedShellUri; } return resolvedShellUri; @@ -1124,8 +1174,7 @@ private string ResolveShellUri(string shell) internal static T ExtractPropertyAsWsManConnectionInfo(RunspaceConnectionInfo rsCI, string property, T defaultValue) { - WSManConnectionInfo wsCI = rsCI as WSManConnectionInfo; - if (wsCI == null) + if (rsCI is not WSManConnectionInfo wsCI) { return defaultValue; } @@ -1326,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; @@ -1415,8 +1464,7 @@ internal bool UseDefaultWSManPort /// /// Default value for shell. /// - private const string DefaultShellUri = - System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix + RemotingConstants.DefaultShellName; + private const string DefaultShellUri = WSManNativeApi.ResourceURIPrefix + RemotingConstants.DefaultShellName; /// /// Default credentials - null indicates credentials of @@ -1546,7 +1594,10 @@ public override string ComputerName /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -1603,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, @@ -1668,7 +1730,10 @@ public int ProcessId /// public string AppDomainName { - get { return _appDomainName; } + get + { + return _appDomainName; + } set { @@ -1701,9 +1766,8 @@ public NamedPipeConnectionInfo() /// Initializes a new instance of the class. /// /// Process Id to connect to. - public NamedPipeConnectionInfo( - int processId) : - this(processId, string.Empty, _defaultOpenTimeout) + public NamedPipeConnectionInfo(int processId) + : this(processId, string.Empty, _defaultOpenTimeout) { } /// @@ -1711,10 +1775,8 @@ public NamedPipeConnectionInfo( /// /// Process Id to connect to. /// Application domain name to connect to, or default AppDomain if blank. - public NamedPipeConnectionInfo( - int processId, - string appDomainName) : - this(processId, appDomainName, _defaultOpenTimeout) + public NamedPipeConnectionInfo(int processId, string appDomainName) + : this(processId, appDomainName, _defaultOpenTimeout) { } /// @@ -1737,9 +1799,8 @@ public NamedPipeConnectionInfo( /// Initializes a new instance of the class. /// /// Pipe name to connect to. - public NamedPipeConnectionInfo( - string customPipeName) : - this(customPipeName, _defaultOpenTimeout) + public NamedPipeConnectionInfo(string customPipeName) + : this(customPipeName, _defaultOpenTimeout) { } /// @@ -1779,7 +1840,10 @@ public override string ComputerName /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -1821,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; @@ -1837,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, @@ -1855,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 /// @@ -1869,7 +1954,7 @@ public string UserName /// /// Key File Path. /// - private string KeyFilePath + public string KeyFilePath { get; set; @@ -1878,7 +1963,7 @@ private string KeyFilePath /// /// Port for connection. /// - private int Port + public int Port { get; set; @@ -1887,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; @@ -1898,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. @@ -1914,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. @@ -1937,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. @@ -1954,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 @@ -2006,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, @@ -2051,27 +2199,69 @@ internal int StartSSHProcess( { string filePath = string.Empty; #if UNIX - string sshCommand = "ssh"; + const string sshCommand = "ssh"; #else - string sshCommand = "ssh.exe"; + const string sshCommand = "ssh.exe"; #endif var context = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); if (context != null) { - var cmdInfo = context.CommandDiscovery.LookupCommandInfo(sshCommand, CommandOrigin.Internal) as ApplicationInfo; - if (cmdInfo != null) + var cmdInfo = CommandDiscovery.LookupCommandInfo( + sshCommand, + CommandTypes.Application, + SearchResolutionOptions.None, + CommandOrigin.Internal, + context); + + if (cmdInfo is ApplicationInfo appInfo) { - filePath = cmdInfo.Path; + filePath = appInfo.Path; } } + else + { + // A Runspace may not be present in the TLS in SDK hosted apps + // or if running in another thread without a Runspace. While + // 'ProcessStartInfo' can lookup the full path in PATH, it searches + // the process' working directory first. 'LookupCommandInfo' does + // not search the process' working directory and we want to keep that + // behavior. We also get the parent dir of the full path to set as the + // new WorkingDirectory. So, we do a manual lookup here only in PATH. + string[] entries = Environment.GetEnvironmentVariable("PATH")?.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + foreach (var path in entries) + { + if (!Path.IsPathFullyQualified(path)) + { + continue; + } + + var sshCommandPath = Path.Combine(path, sshCommand); + if (File.Exists(sshCommandPath)) + { + filePath = sshCommandPath; + break; + } + } + } + + if (string.IsNullOrEmpty(filePath)) + { + throw new CommandNotFoundException( + sshCommand, + null, + "CommandNotFoundException", + DiscoveryExceptions.CommandNotFoundException); + } - // Create a local ssh process (client) that conects to a remote sshd process (server) using a 'powershell' subsystem. + // Create a local ssh process (client) that connects to a remote sshd process (server) using a 'powershell' subsystem. // // Local ssh invoked as: // windows: - // 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) @@ -2082,36 +2272,37 @@ internal int StartSSHProcess( // linux|macos: // Subsystem powershell /usr/local/bin/pwsh -SSHServerMode -NoLogo -NoProfile - System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(filePath); + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified, so any file executed in the runspace would be in the user's local system/process or a system they have access to in which case restricted remoting security guidelines should be used. + ProcessStartInfo startInfo = new(filePath); // pass "-i identity_file" command line argument to ssh if KeyFilePath is set // if KeyFilePath is not set, then ssh will use IdentityFile / IdentityAgent from ssh_config if defined else none by default if (!string.IsNullOrEmpty(this.KeyFilePath)) { - if (!System.IO.File.Exists(this.KeyFilePath)) + if (!File.Exists(this.KeyFilePath)) { throw new FileNotFoundException( StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath)); } - 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}")); } } @@ -2119,28 +2310,37 @@ 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.WorkingDirectory = Path.GetDirectoryName(filePath); startInfo.CreateNoWindow = true; startInfo.UseShellExecute = false; 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 @@ -2180,6 +2380,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 // @@ -2230,23 +2440,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; @@ -2287,9 +2505,9 @@ 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 private static int StartSSHProcessImpl( - System.Diagnostics.ProcessStartInfo startInfo, + ProcessStartInfo startInfo, out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { Exception ex = null; - System.Diagnostics.Process sshProcess = null; + Process sshProcess = null; // // These std pipe handles are bound to managed Reader/Writer objects and returned to the transport // manager object, which uses them for PSRP communication. The lifetime of these handles are then @@ -2450,7 +2668,7 @@ private static int StartSSHProcessImpl( catch (InvalidOperationException e) { ex = e; } catch (ArgumentException e) { ex = e; } catch (FileNotFoundException e) { ex = e; } - catch (System.ComponentModel.Win32Exception e) { ex = e; } + catch (Win32Exception e) { ex = e; } if ((ex != null) || (sshProcess == null) || @@ -2475,9 +2693,9 @@ private static int StartSSHProcessImpl( { if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } - if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } + if (stdOutReaderVar != null) { stdOutReaderVar.Dispose(); } else { stdOutPipeServer.Dispose(); } - if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } + if (stdErrReaderVar != null) { stdErrReaderVar.Dispose(); } else { stdErrPipeServer.Dispose(); } throw; } @@ -2485,6 +2703,17 @@ private static int StartSSHProcessImpl( return sshProcess.Id; } + private static void KillSSHProcessImpl(int pid) + { + using (var sshProcess = 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; @@ -2504,10 +2733,10 @@ private static Process CreateProcessWithRedirectedStd( stdInPipeServer = null; stdOutPipeServer = null; stdErrPipeServer = null; - SafePipeHandle stdInPipeClient = null; - SafePipeHandle stdOutPipeClient = null; - SafePipeHandle stdErrPipeClient = null; - string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); + SafeFileHandle stdInPipeClient = null; + SafeFileHandle stdOutPipeClient = null; + SafeFileHandle stdErrPipeClient = null; + string randomName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()); try { @@ -2528,17 +2757,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; } @@ -2557,9 +2781,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 @@ -2604,51 +2828,23 @@ 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(); + stdOutPipeServer?.Dispose(); + stdErrPipeServer?.Dispose(); throw; } finally { + lpStartupInfo.Dispose(); lpProcessInformation.Dispose(); } } - private static SafePipeHandle GetNamedPipeHandle(string pipeName) + private static SafeFileHandle GetNamedPipeHandle(string pipeName) { - // Create pipe flags for asynchronous pipes. - uint pipeFlags = NamedPipeNative.FILE_FLAG_OVERLAPPED; - - // We want an inheritable handle. - PlatformInvokes.SECURITY_ATTRIBUTES securityAttributes = new PlatformInvokes.SECURITY_ATTRIBUTES(); - - // Get handle to pipe. - var fileHandle = PlatformInvokes.CreateFileW( - pipeName, - NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, - 0, - securityAttributes, - NamedPipeNative.OPEN_EXISTING, - pipeFlags, - 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( @@ -2678,10 +2874,7 @@ private static SafePipeHandle CreateNamedPipe( securityAttributes); int lastError = Marshal.GetLastWin32Error(); - if (securityDescHandle != null) - { - securityDescHandle.Value.Free(); - } + securityDescHandle?.Free(); if (pipeHandle.IsInvalid) { @@ -2768,7 +2961,10 @@ public override string CertificateThumbprint /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -2782,13 +2978,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, @@ -2893,7 +3100,10 @@ public override string CertificateThumbprint /// public override PSCredential Credential { - get { return _credential; } + get + { + return _credential; + } set { @@ -2912,13 +3122,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) { @@ -3443,7 +3664,26 @@ private void GetContainerPropertiesInternal() // if (RuntimeId == Guid.Empty) { - ContainerObRoot = (string)computeSystemPropertiesType.GetProperty("ObRoot").GetValue(computeSystemPropertiesHandle); + // Since Hyper-V changed this from a property to a field, we can optimize for newest Windows to see if it's a field, + // otherwise we fall back to old code to be compatible with older versions of Windows + var obRootFieldInfo = computeSystemPropertiesType.GetField("ObRoot"); + if (obRootFieldInfo != null) + { + ContainerObRoot = obRootFieldInfo.GetValue(computeSystemPropertiesHandle) as string; + } + else + { + var obRootPropertyInfo = computeSystemPropertiesType.GetProperty("ObRoot"); + if (obRootPropertyInfo != null) + { + ContainerObRoot = obRootPropertyInfo.GetValue(computeSystemPropertiesHandle) as string; + } + } + + if (ContainerObRoot == null) + { + throw new PSInvalidOperationException(RemotingErrorIdStrings.CannotGetHostInteropTypes); + } } } } @@ -3475,7 +3715,7 @@ private void GetContainerPropertiesInternal() /// /// Run some tasks on MTA thread if needed. /// - private void RunOnMTAThread(ThreadStart threadProc) + private static void RunOnMTAThread(ThreadStart threadProc) { if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { @@ -3494,7 +3734,7 @@ private void RunOnMTAThread(ThreadStart threadProc) /// /// Get error message from the thrown exception. /// - private string GetErrorMessageFromException(Exception e) + private static string GetErrorMessageFromException(Exception e) { string errorMessage = e.Message; 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 85ba02b2e7b..e26f3421cda 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,12 +74,13 @@ 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); - internal static readonly Version ProtocolVersionWin8RTM = new Version(2, 2); - internal static readonly Version ProtocolVersionWin10RTM = new Version(2, 3); + internal static readonly Version ProtocolVersion_2_0 = new(2, 0); // Window 7 RC + internal static readonly Version ProtocolVersion_2_1 = new(2, 1); // Window 7 RTM + internal static readonly Version ProtocolVersion_2_2 = new(2, 2); // Window 8 RTM + internal static readonly Version ProtocolVersion_2_3 = new(2, 3); // Window 10 RTM + internal static readonly Version ProtocolVersion_2_4 = new(2, 4); // PowerShell 7.6 // Minor will be incremented for each change in PSRP client/server stack and new versions will be // forked on early major release/drop changes history. @@ -86,7 +88,15 @@ internal static class RemotingConstants // 2.102 to 2.103 - Key exchange protocol changes in M3 // 2.103 to 2.2 - Final ship protocol version value, no change to protocol // 2.2 to 2.3 - Enabling informational stream - internal static readonly Version ProtocolVersionCurrent = new Version(2, 3); + // 2.3 to 2.4 - Deprecate the 'Session_Key' exchange. The following messages are obsolete when both server and client are v2.4+: + // - PUBLIC_KEY + // - PUBLIC_KEY_REQUEST + // - ENCRYPTED_SESSION_KEY + // The padding algorithm 'RSAEncryptionPadding.Pkcs1' used in the 'Session_Key' exchange is NOT secure, and therefore, + // PSRP needs to be used on top of a secure transport and the 'Session_Key' doesn't add any extra security. + // So, we decided to deprecate the 'Session_Key' exchange in PSRP and skip encryption and decryption for 'SecureString' + // objects. Instead, we require the transport to be secure for secure data transfer between PSRP clients and servers. + internal static readonly Version ProtocolVersionCurrent = new(2, 4); internal static readonly Version ProtocolVersion = ProtocolVersionCurrent; // Used by remoting commands to add remoting specific note properties. internal static readonly string ComputerNameNoteProperty = "PSComputerName"; @@ -124,234 +134,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"; @@ -1196,10 +978,10 @@ internal static RemoteDataObject GenerateRunspacePoolStateInfo( // Add Reason property if (stateInfo.Reason != null) { - // If Reason is of not type IContainsErrorRecord, a new ErrorRecord is - // created using this errorId - string errorId = "RemoteRunspaceStateInfoReason"; - PSNoteProperty exceptionProperty = GetExceptionProperty(stateInfo.Reason, errorId, ErrorCategory.NotSpecified); + PSNoteProperty exceptionProperty = GetExceptionProperty( + exception: stateInfo.Reason, + errorId: "RemoteRunspaceStateInfoReason", + category: ErrorCategory.NotSpecified); dataAsPSObject.Properties.Add(exceptionProperty); } @@ -1515,12 +1297,10 @@ internal static RemoteDataObject GeneratePowerShellStateInfo(PSInvocationStateIn // Add exception property if (stateInfo.Reason != null) { - // If Reason is of not type IContainsErrorRecord, - // a new ErrorRecord is created using this errorId - string errorId = "RemotePSInvocationStateInfoReason"; - PSNoteProperty exceptionProperty = - GetExceptionProperty(stateInfo.Reason, errorId, - ErrorCategory.NotSpecified); + PSNoteProperty exceptionProperty = GetExceptionProperty( + exception: stateInfo.Reason, + errorId: "RemotePSInvocationStateInfoReason", + category: ErrorCategory.NotSpecified); dataAsPSObject.Properties.Add(exceptionProperty); } @@ -1541,7 +1321,7 @@ internal static RemoteDataObject GeneratePowerShellStateInfo(PSInvocationStateIn /// /// /// ErrorRecord if exception is of type IContainsErrorRecord - /// Null if if exception is not of type IContainsErrorRecord + /// Null if exception is not of type IContainsErrorRecord /// internal static ErrorRecord GetErrorRecordFromException(Exception exception) { @@ -1563,6 +1343,9 @@ internal static ErrorRecord GetErrorRecordFromException(Exception exception) /// /// Gets a Note Property for the exception. /// + /// + /// If is of not type IContainsErrorRecord, a new ErrorRecord is created. + /// /// /// ErrorId to use if exception is not of type IContainsErrorRecord. /// ErrorCategory to use if exception is not of type IContainsErrorRecord. @@ -1599,8 +1382,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); } @@ -1802,7 +1583,7 @@ internal static IEnumerable EnumerateListProperty(PSObject psObject, strin } } - internal static IEnumerable> EnumerateHashtableProperty(PSObject psObject, string propertyName) + internal static IEnumerable> EnumerateHashtableProperty(PSObject psObject, string propertyName) { if (psObject == null) { @@ -1819,9 +1600,9 @@ internal static IEnumerable> EnumerateHashtable { foreach (DictionaryEntry e in h) { - KeyType key = ConvertPropertyValueTo(propertyName, e.Key); - ValueType value = ConvertPropertyValueTo(propertyName, e.Value); - yield return new KeyValuePair(key, value); + TKey key = ConvertPropertyValueTo(propertyName, e.Key); + TValue value = ConvertPropertyValueTo(propertyName, e.Value); + yield return new KeyValuePair(key, value); } } } @@ -2088,8 +1869,7 @@ internal static object GetPowerShellOutput(object data) /// PSInvocationInfo. internal static PSInvocationStateInfo GetPowerShellStateInfo(object data) { - PSObject dataAsPSObject = data as PSObject; - if (dataAsPSObject == null) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.DecodingErrorForPowerShellStateInfo); @@ -2237,7 +2017,7 @@ internal static PowerShell GetCommandDiscoveryPipeline(object data) } else { - module = new string[] { "" }; + module = new string[] { string.Empty }; } ModuleSpecification[] fullyQualifiedName = null; @@ -2281,7 +2061,7 @@ internal static PowerShell GetCommandDiscoveryPipeline(object data) /// Gets the NoInput setting from the specified data. /// /// Data to decode. - /// true if there is no pipeline input; false otherwise. + /// if there is no pipeline input; otherwise. internal static bool GetNoInput(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2298,7 +2078,7 @@ internal static bool GetNoInput(object data) /// Gets the AddToHistory setting from the specified data. /// /// Data to decode. - /// true if there is addToHistory data; false otherwise. + /// if there is addToHistory data; otherwise. internal static bool GetAddToHistory(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2315,7 +2095,7 @@ internal static bool GetAddToHistory(object data) /// Gets the IsNested setting from the specified data. /// /// Data to decode. - /// true if there is IsNested data; false otherwise. + /// if there is IsNested data; otherwise. internal static bool GetIsNested(object data) { PSObject dataAsPSObject = PSObject.AsPSObject(data); @@ -2357,9 +2137,7 @@ internal static RemoteStreamOptions GetRemoteStreamOptions(object data) /// RemoteSessionCapability object. internal static RemoteSessionCapability GetSessionCapability(object data) { - PSObject dataAsPSObject = data as PSObject; - - if (dataAsPSObject == null) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.CantCastRemotingDataToPSObject, data.GetType().FullName); @@ -2374,24 +2152,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; } @@ -2407,7 +2167,7 @@ internal static bool ServerSupportsBatchInvocation(Runspace runspace) return false; } - return (runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersionWin8RTM); + return (runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersion_2_2); } } } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteDebuggingCapability.cs index 8314337b77a..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,11 +13,11 @@ 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(); - internal Version PSVersion { get; private set; } + internal Version PSVersion { get; } /// /// Initializes a new instance of the class. @@ -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 414676dffed..79b920c22b5 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/RemoteHost.cs @@ -42,12 +42,12 @@ internal string MethodName /// /// Method info. /// - private RemoteHostMethodInfo _methodInfo; + private readonly RemoteHostMethodInfo _methodInfo; /// /// Call id. /// - private long _callId; + private readonly long _callId; /// /// Call id. @@ -188,17 +188,14 @@ internal void ExecuteVoidMethod(PSHost clientHost) } finally { - if (remoteRunspaceToClose != null) - { - remoteRunspaceToClose.Close(); - } + remoteRunspaceToClose?.Close(); } } /// /// Get remote runspace to close. /// - private RemoteRunspace GetRemoteRunspaceToClose(PSHost clientHost) + private static RemoteRunspace GetRemoteRunspaceToClose(PSHost clientHost) { // Figure out if we need to close the remote runspace. Return null if we don't. @@ -440,7 +437,7 @@ internal Collection PerformSecurityChecksOnHostMessage(string co /// /// Caption to modify. /// New modified caption. - private string ModifyCaption(string caption) + private static string ModifyCaption(string caption) { string pscaption = CredUI.PromptForCredential_DefaultCaption; @@ -465,7 +462,7 @@ private string ModifyCaption(string caption) /// computername to include in the /// message /// Message which contains a warning as well. - private string ModifyMessage(string message, string computerName) + private static string ModifyMessage(string message, string computerName) { string modifiedMessage = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.RemoteHostPromptForCredentialModifiedMessage, @@ -485,7 +482,7 @@ private string ModifyMessage(string message, string computerName) /// Resource string to use. /// A constructed remote host call message /// which will display the warning. - private RemoteHostCall ConstructWarningMessageForSecureString(string computerName, + private static RemoteHostCall ConstructWarningMessageForSecureString(string computerName, string resourceString) { string warning = PSRemotingErrorInvariants.FormatResourceString( @@ -506,7 +503,7 @@ private RemoteHostCall ConstructWarningMessageForSecureString(string computerNam /// in warning /// A constructed remote host call message /// which will display the warning. - private RemoteHostCall ConstructWarningMessageForGetBufferContents(string computerName) + private static RemoteHostCall ConstructWarningMessageForGetBufferContents(string computerName) { string warning = PSRemotingErrorInvariants.FormatResourceString( RemotingErrorIdStrings.RemoteHostGetBufferContents, @@ -530,7 +527,7 @@ internal class RemoteHostResponse /// /// Call id. /// - private long _callId; + private readonly long _callId; /// /// Call id. @@ -546,17 +543,17 @@ internal long CallId /// /// Method id. /// - private RemoteHostMethodId _methodId; + private readonly RemoteHostMethodId _methodId; /// /// Return value. /// - private object _returnValue; + private readonly object _returnValue; /// /// Exception. /// - private Exception _exception; + private readonly Exception _exception; /// /// Constructor for RemoteHostResponse. 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 8a60ce277a8..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; @@ -24,7 +25,7 @@ namespace System.Management.Automation.Remoting /// guarantees that transmitting on the wire will not change the encoded /// object's type. /// - internal class RemoteHostEncoder + internal static class RemoteHostEncoder { /// /// Is known type. @@ -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) @@ -744,7 +745,7 @@ private static IDictionary DecodeObjectDictionary(PSObject psObject, Type dictio /// private static T SafelyGetBaseObject(PSObject psObject) { - if (psObject == null || psObject.BaseObject == null || !(psObject.BaseObject is T)) + if (psObject == null || psObject.BaseObject == null || psObject.BaseObject is not T) { throw RemoteHostExceptions.NewDecodingFailedException(); } @@ -771,7 +772,7 @@ private static T SafelyCastObject(object obj) private static T SafelyGetPropertyValue(PSObject psObject, string key) { PSPropertyInfo propertyInfo = psObject.Properties[key]; - if (propertyInfo == null || propertyInfo.Value == null || !(propertyInfo.Value is T)) + if (propertyInfo == null || propertyInfo.Value == null || propertyInfo.Value is not T) { throw RemoteHostExceptions.NewDecodingFailedException(); } 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 bc5d47c8a83..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; @@ -21,12 +20,10 @@ internal class RemoteSessionCapability { #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell - private Version _psversion; - private Version _serversion; + private readonly Version _psversion; + private readonly Version _serversion; private Version _protocolVersion; - private RemotingDestination _remotingDestination; - private static byte[] _timeZoneInByteFormat; - private TimeZoneInfo _timeZone; + private readonly RemotingDestination _remotingDestination; #endregion @@ -91,62 +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()) - { - formatter.Serialize(stream, TimeZoneInfo.Local); - 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; } - } } /// @@ -169,15 +110,14 @@ internal enum HostDefaultDataId /// /// The HostDefaultData class. /// - internal class HostDefaultData + internal sealed class HostDefaultData { /// /// Data. /// - #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell - private Dictionary data; + private readonly Dictionary data; #endregion @@ -358,7 +298,7 @@ internal bool IsHostNull /// /// Is host ui null. /// - private bool _isHostUINull; + private readonly bool _isHostUINull; /// /// Is host ui null. @@ -374,7 +314,7 @@ internal bool IsHostUINull /// /// Is host raw ui null. /// - private bool _isHostRawUINull; + private readonly bool _isHostRawUINull; private readonly bool _isHostNull; @@ -447,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 664c2a91ae0..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 @@ -213,7 +213,7 @@ private void SerializeHeader(Stream streamToWriteTo) return; } - private void SerializeUInt(uint data, Stream streamToWriteTo) + private static void SerializeUInt(uint data, Stream streamToWriteTo) { Dbg.Assert(streamToWriteTo != null, "stream to write to cannot be null"); @@ -241,7 +241,7 @@ private static uint DeserializeUInt(Stream serializedDataStream) return result; } - private void SerializeGuid(Guid guid, Stream streamToWriteTo) + private static void SerializeGuid(Guid guid, Stream streamToWriteTo) { Dbg.Assert(streamToWriteTo != null, "stream to write to cannot be null"); @@ -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 2ebe434ea04..c451ba32f45 100644 --- a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs +++ b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs @@ -98,14 +98,14 @@ internal FragmentedRemoteObject() internal FragmentedRemoteObject(byte[] blob, long objectId, long fragmentId, bool isEndFragment) { - Dbg.Assert((blob != null) || (blob.Length == 0), "Cannot create a fragment for null or empty data."); + Dbg.Assert((blob != null) && (blob.Length != 0), "Cannot create a fragment for null or empty data."); Dbg.Assert(objectId >= 0, "Object Id cannot be < 0"); Dbg.Assert(fragmentId >= 0, "Fragment Id cannot be < 0"); ObjectId = objectId; FragmentId = fragmentId; - IsStartFragment = (fragmentId == 0) ? true : false; + IsStartFragment = fragmentId == 0; IsEndFragment = isEndFragment; _blob = blob; @@ -142,7 +142,10 @@ internal FragmentedRemoteObject(byte[] blob, long objectId, long fragmentId, /// internal int BlobLength { - get { return _blobLength; } + get + { + return _blobLength; + } set { @@ -156,7 +159,10 @@ internal int BlobLength /// internal byte[] Blob { - get { return _blob; } + get + { + return _blob; + } set { @@ -205,10 +211,10 @@ internal byte[] Blob /// internal byte[] GetBytes() { - int objectIdSize = 8; // number of bytes of long - int fragmentIdSize = 8; // number of bytes of long - int flagsSize = 1; // 1 byte for IsEndOfFrag and IsControl - int blobLengthSize = 4; // number of bytes of int + const int objectIdSize = 8; // number of bytes of long + const int fragmentIdSize = 8; // number of bytes of long + const int flagsSize = 1; // 1 byte for IsEndOfFrag and IsControl + const int blobLengthSize = 4; // number of bytes of int int totalLength = objectIdSize + fragmentIdSize + flagsSize + blobLengthSize + BlobLength; @@ -364,7 +370,7 @@ internal static bool GetIsStartFragment(byte[] fragmentBytes, int startIndex) /// /// /// - /// True if the the E-flag is set in the encoding. Otherwise false. + /// True if the E-flag is set in the encoding. Otherwise false. /// /// /// If fragmentBytes is null. @@ -426,8 +432,8 @@ internal static int GetBlobLength(byte[] fragmentBytes, int startIndex) /// internal class SerializedDataStream : Stream, IDisposable { - [TraceSourceAttribute("SerializedDataStream", "SerializedDataStream")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("SerializedDataStream", "SerializedDataStream"); + [TraceSource("SerializedDataStream", "SerializedDataStream")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("SerializedDataStream", "SerializedDataStream"); #region Global Constants private static long s_objectIdSequenceNumber = 0; @@ -437,20 +443,20 @@ internal class SerializedDataStream : Stream, IDisposable #region Private Data private bool _isEntered; - private FragmentedRemoteObject _currentFragment; + private readonly FragmentedRemoteObject _currentFragment; private long _fragmentId; - private int _fragmentSize; - private object _syncObject; + private readonly int _fragmentSize; + private readonly object _syncObject; private bool _isDisposed; - private bool _notifyOnWriteFragmentImmediately; + private readonly bool _notifyOnWriteFragmentImmediately; // MemoryStream does not dynamically resize as data is read. This will waste // lot of memory as data sent on the network will still be there in memory. // To avoid this a queue of memory streams (each stream is of fragmentsize) // is created..so after data is sent the MemoryStream is disposed there by // clearing resources. - private Queue _queuedStreams; + private readonly Queue _queuedStreams; private MemoryStream _writeStream; private MemoryStream _readStream; private int _writeOffset; @@ -578,7 +584,7 @@ public override void Write(byte[] buffer, int offset, int count) if (dataLeftInTheFragment > 0) { int amountToWriteIntoFragment = (amountLeft > dataLeftInTheFragment) ? dataLeftInTheFragment : amountLeft; - amountLeft = amountLeft - amountToWriteIntoFragment; + amountLeft -= amountToWriteIntoFragment; // Write data into fragment Array.Copy(buffer, offsetToReadFrom, _currentFragment.Blob, _currentFragment.BlobLength, amountToWriteIntoFragment); @@ -751,11 +757,11 @@ private void WriteCurrentFragmentAndReset() PSEtwLog.LogAnalyticVerbose( PSEventId.SentRemotingFragment, PSOpcode.Send, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - (Int64)(_currentFragment.ObjectId), - (Int64)(_currentFragment.FragmentId), + (long)(_currentFragment.ObjectId), + (long)(_currentFragment.FragmentId), _currentFragment.IsStartFragment ? 1 : 0, _currentFragment.IsEndFragment ? 1 : 0, - (UInt32)(_currentFragment.BlobLength), + (uint)(_currentFragment.BlobLength), new PSETWBinaryBlob(_currentFragment.Blob, 0, _currentFragment.BlobLength)); // finally write into memory stream @@ -799,7 +805,7 @@ private void WriteCurrentFragmentAndReset() } int amountToWriteIntoStream = (amountLeft > dataLeftInWriteStream) ? dataLeftInWriteStream : amountLeft; - amountLeft = amountLeft - amountToWriteIntoStream; + amountLeft -= amountToWriteIntoStream; // write data _writeStream.Position = _writeOffset; _writeStream.Write(data, offSetToReadFrom, amountToWriteIntoStream); @@ -811,10 +817,7 @@ private void WriteCurrentFragmentAndReset() } // call the callback since we have data available - if (_onDataAvailableCallback != null) - { - _onDataAvailableCallback(data, _currentFragment.IsEndFragment); - } + _onDataAvailableCallback?.Invoke(data, _currentFragment.IsEndFragment); // prepare a new fragment _currentFragment.FragmentId = ++_fragmentId; @@ -886,9 +889,11 @@ protected override void Dispose(bool disposing) /// /// public override bool CanRead { get { return true; } } + /// /// public override bool CanSeek { get { return false; } } + /// /// public override bool CanWrite { get { return true; } } @@ -896,6 +901,7 @@ protected override void Dispose(bool disposing) /// Gets the length of the stream in bytes. /// public override long Length { get { return _length; } } + /// /// public override long Position @@ -911,6 +917,7 @@ public override long Position public override void Flush() { } + /// /// /// @@ -920,6 +927,7 @@ public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + /// /// /// @@ -957,13 +965,13 @@ public override void SetLength(long value) internal class Fragmentor { #region Global Constants - private static UTF8Encoding s_utf8Encoding = new UTF8Encoding(); + private static readonly UTF8Encoding s_utf8Encoding = new UTF8Encoding(); // This const defines the default depth to be used for serializing objects for remoting. private const int SerializationDepthForRemoting = 1; #endregion private int _fragmentSize; - private SerializationContext _serializationContext; + private readonly SerializationContext _serializationContext; #region Constructor diff --git a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs index fa1bcb7da86..f97b2b21d1f 100644 --- a/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs +++ b/src/System.Management.Automation/engine/remoting/common/psstreamobject.cs @@ -75,7 +75,9 @@ public class PSStreamObject /// /// public PSStreamObjectType ObjectType { get; set; } + internal object Value { get; set; } + internal Guid Id { get; set; } internal PSStreamObject(PSStreamObjectType objectType, object value, Guid id) @@ -89,8 +91,8 @@ internal PSStreamObject(PSStreamObjectType objectType, object value, Guid id) /// /// /// - public PSStreamObject(PSStreamObjectType objectType, object value) : - this(objectType, value, Guid.Empty) + public PSStreamObject(PSStreamObjectType objectType, object value) + : this(objectType, value, Guid.Empty) { } @@ -118,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; @@ -131,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; @@ -144,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; @@ -157,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; @@ -168,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; @@ -179,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; @@ -191,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; @@ -244,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]; } @@ -309,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; @@ -322,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; @@ -335,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; @@ -363,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; @@ -376,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; @@ -409,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; @@ -468,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,39 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. /// @@ -549,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 @@ -589,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. @@ -613,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(); } /// @@ -647,28 +616,6 @@ internal PSRemotingTransportRedirectException(string redirectLocation, PSRemotin #endregion - #region Public overrides - - /// - /// Serializes the exception data. - /// - /// Serialization information. - /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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. @@ -681,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 9fa2233153f..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 @@ -204,6 +204,11 @@ internal class ThrottleManager : IDisposable /// internal int ThrottleLimit { + get + { + return _throttleLimit; + } + set { if (value > 0 && value <= s_THROTTLE_LIMIT_MAX) @@ -211,11 +216,6 @@ internal int ThrottleLimit _throttleLimit = value; } } - - get - { - return _throttleLimit; - } } private int _throttleLimit = s_DEFAULT_THROTTLE_LIMIT; @@ -532,10 +532,7 @@ private void StartOneOperationFromQueue() } } - if (operation != null) - { - operation.StartOperation(); - } + operation?.StartOperation(); } /// @@ -599,34 +596,34 @@ private void RaiseThrottleManagerEvents() /// Default throttle limit - the maximum number of operations /// to be processed at a time. /// - private static int s_DEFAULT_THROTTLE_LIMIT = 32; + private static readonly int s_DEFAULT_THROTTLE_LIMIT = 32; /// /// Maximum value that the throttle limit can be set to. /// - private static int s_THROTTLE_LIMIT_MAX = int.MaxValue; + private static readonly int s_THROTTLE_LIMIT_MAX = int.MaxValue; /// /// All pending operations. /// - private List _operationsQueue; + private readonly List _operationsQueue; /// /// List of items on which a StartOperation has /// been called. /// - private List _startOperationQueue; + private readonly List _startOperationQueue; /// /// List of items on which a StopOperation has /// been called. /// - private List _stopOperationQueue; + private readonly List _stopOperationQueue; /// /// Object used to synchronize access to the queues. /// - private object _syncObject; + private readonly object _syncObject; private bool _submitComplete = false; // to check if operations have been submitComplete private bool _stopping = false; // if stop is in process @@ -675,9 +672,9 @@ internal class Operation : IThrottleOperation private Thread workerThreadStart; private Thread workerThreadStop; - public bool Done { set; get; } + public bool Done { get; set; } - public int SleepTime { set; get; } = 100; + public int SleepTime { get; set; } = 100; private void WorkerThreadMethodStart() { diff --git a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs index 0f7f46438cd..c3cf3b278aa 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; @@ -97,7 +152,7 @@ internal enum ConnectionStatus AutoDisconnectStarting = 4, AutoDisconnectSucceeded = 5, InternalErrorAbort = 6 - }; + } /// /// ConnectionStatusEventArgs. @@ -136,12 +191,12 @@ 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")] - private static PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); + [TraceSource("Transport", "Traces BaseWSManTransportManager")] + private static readonly PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); #endregion @@ -158,7 +213,7 @@ internal abstract class BaseTransportManager : IDisposable // This value instructs the server to use whatever setting it has for idle timeout. internal const int UseServerDefaultIdleTimeout = -1; - internal const uint UseServerDefaultIdleTimeoutUInt = UInt32.MaxValue; + internal const uint UseServerDefaultIdleTimeoutUInt = uint.MaxValue; // Minimum allowed idle timeout time is 60 seconds. internal const int MinimumIdleTimeout = 60 * 1000; @@ -178,7 +233,7 @@ internal abstract class BaseTransportManager : IDisposable #region Private Data // fragmentor used to fragment & defragment objects added to this collection. - private ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; + private readonly ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; // crypto helper used for encrypting/decrypting // secure string @@ -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)); @@ -334,9 +390,9 @@ internal void OnDataAvailableCallback(RemoteDataObject remoteObject) PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, remoteObject.RunspacePoolId.ToString(), remoteObject.PowerShellId.ToString(), - (UInt32)(remoteObject.Destination), - (UInt32)(remoteObject.DataType), - (UInt32)(remoteObject.TargetInterface)); + (uint)(remoteObject.Destination), + (uint)(remoteObject.DataType), + (uint)(remoteObject.TargetInterface)); // This might throw exceptions which the caller handles. PowerShellGuidObserver.SafeInvoke(remoteObject.PowerShellId, EventArgs.Empty); @@ -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,21 +466,24 @@ 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 Queue _callbackNotificationQueue; - private ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; + private readonly Queue _callbackNotificationQueue; + private readonly ReceiveDataCollection.OnDataAvailableCallback _onDataAvailableCallback; private bool _isServicingCallbacks; private bool _suspendQueueServicing; private bool _isDebuggerSuspend; @@ -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; @@ -757,7 +819,7 @@ internal void EnqueueAndStartProcessingThread(RemoteDataObject remoteO /// /// Remote data object. /// True if remote data object requires a user response. - private bool CheckForInteractiveHostCall(RemoteDataObject remoteObject) + private static bool CheckForInteractiveHostCall(RemoteDataObject remoteObject) { bool interactiveHostCall = false; @@ -805,7 +867,7 @@ internal void ServicePendingCallbacks(object objectToProcess) try { - do + while (true) { // if the transport manager is closed return. if (isClosed) @@ -843,7 +905,7 @@ internal void ServicePendingCallbacks(object objectToProcess) base.OnDataAvailableCallback(rcvdDataInfo.remoteObject); } } - } while (true); + } } catch (Exception exception) { @@ -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(); @@ -972,7 +1037,7 @@ internal virtual void PrepareForConnect() #region Clean up /// - /// Finalizer. + /// Finalizes an instance of the class. /// ~BaseClientTransportManager() { @@ -983,10 +1048,7 @@ internal virtual void PrepareForConnect() else { // wait for the close to be completed and then release the resources. - this.CloseCompleted += delegate (object source, EventArgs args) - { - Dispose(false); - }; + this.CloseCompleted += (object source, EventArgs args) => Dispose(false); try { @@ -1001,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; @@ -1017,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) { } @@ -1170,7 +1238,7 @@ internal void RaiseSignalCompleted() #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1217,9 +1285,9 @@ internal abstract class AbstractServerTransportManager : BaseTransportManager { #region Private Data - private object _syncObject = new object(); + private readonly object _syncObject = new object(); // used to listen to data available events from serialized datastream. - private SerializedDataStream.OnDataAvailableCallback _onDataAvailable; + private readonly SerializedDataStream.OnDataAvailableCallback _onDataAvailable; // the following variable are used by onDataAvailableCallback. private bool _shouldFlushData; private bool _reportAsPending; @@ -1292,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; @@ -1349,10 +1414,10 @@ private void OnDataAvailable(byte[] dataToSend, bool isEndFragment) _runspacePoolInstanceId.ToString(), _powerShellInstanceId.ToString(), dataToSend.Length.ToString(CultureInfo.InvariantCulture), - (UInt32)_dataType, - (UInt32)_targetInterface); + (uint)_dataType, + (uint)_targetInterface); - SendDataToClient(dataToSend, (isEndFragment & _shouldFlushData) ? true : false, _reportAsPending, isEndFragment); + SendDataToClient(dataToSend, isEndFragment && _shouldFlushData, _reportAsPending, isEndFragment); } /// @@ -1391,7 +1456,7 @@ internal void ReportError(int errorCode, string methodName) // Use thread-pool thread to raise the error handler..see explanation // in the method summary ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object state) + (object state) => { TransportErrorOccuredEventArgs eventArgs = new TransportErrorOccuredEventArgs(e, TransportMethodEnum.Unknown); @@ -1527,7 +1592,7 @@ internal static byte[] ExtractEncodedXmlElement(string xmlBuffer, string xmlTag) XmlReader reader = XmlReader.Create(new StringReader(xmlBuffer), readerSettings); string additionalData; - if (XmlNodeType.Element == reader.MoveToContent()) + if (reader.MoveToContent() == XmlNodeType.Element) { additionalData = reader.ReadElementContentAsString(xmlTag, reader.NamespaceURI); } diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 1a9642ca7c2..07e17e8b63a 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. /// @@ -157,7 +165,7 @@ private void Update(string optionName, string optionValue) /// /// 1. "optionName" is already defined /// - private void AssertValueNotAssigned(string optionName, object originalValue) + private static void AssertValueNotAssigned(string optionName, object originalValue) { if (originalValue != null) { @@ -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 @@ -401,7 +405,7 @@ public virtual InitialSessionState GetInitialSessionState(PSSessionConfiguration /// /// User Identity for which this information is requested /// - /// Application private data or null + /// Application private data or public virtual PSPrimitiveDictionary GetApplicationPrivateData(PSSenderInfo senderInfo) { return null; @@ -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; @@ -785,10 +790,10 @@ private static string private const string configProviderApplicationBaseKeyName = "ApplicationBase"; private const string configProviderAssemblyNameKeyName = "AssemblyName"; - private static Dictionary s_ssnStateProviders = + private static readonly Dictionary s_ssnStateProviders = new Dictionary(StringComparer.OrdinalIgnoreCase); - private static object s_syncObject = new object(); + private static readonly object s_syncObject = new object(); #endregion } @@ -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)), }; /// @@ -1209,7 +1230,7 @@ private static bool FunctionDefinitionsTypeValidationCallback(string key, object return false; } - if ((hashtable[FunctionValueToken] as ScriptBlock) == null) + if (hashtable[FunctionValueToken] is not ScriptBlock) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCKeyMustBeScriptBlock, FunctionValueToken, key, path)); return false; @@ -1285,7 +1306,7 @@ private static bool VariableDefinitionsTypeValidationCallback(string key, object /// private static bool StringTypeValidationCallback(string key, object obj, PSCmdlet cmdlet, string path) { - if (!(obj is string)) + if (obj is not string) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeMustBeString, key, path)); return false; @@ -1315,7 +1336,7 @@ private static bool StringArrayTypeValidationCallback(string key, object obj, PS private static bool BooleanTypeValidationCallback(string key, object obj, PSCmdlet cmdlet, string path) { - if (!(obj is bool)) + if (obj is not bool) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeMustBeBoolean, key, path)); return false; @@ -1326,7 +1347,7 @@ private static bool BooleanTypeValidationCallback(string key, object obj, PSCmdl private static bool IntegerTypeValidationCallback(string key, object obj, PSCmdlet cmdlet, string path) { - if (!(obj is int) && !(obj is long)) + if (obj is not int && obj is not long) { cmdlet.WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.DISCTypeMustBeInteger, key, path)); return false; @@ -1355,6 +1376,8 @@ private static bool StringOrHashtableArrayTypeValidationCallback(string key, obj } } + #endregion + #region DISC Utilities /// @@ -1634,7 +1657,7 @@ internal static void ValidateRoleDefinitions(IDictionary roleDefinitions) { foreach (var roleKey in roleDefinitions.Keys) { - if (!(roleKey is string)) + if (roleKey is not string) { var invalidOperationEx = new PSInvalidOperationException( string.Format(RemotingErrorIdStrings.InvalidRoleKeyType, roleKey.GetType().FullName)); @@ -1681,13 +1704,15 @@ internal static void ValidateRoleDefinitions(IDictionary roleDefinitions) #endregion + #region DISCPowerShellConfiguration + /// /// Creates an initial session state based on the configuration language for PSSC files. /// internal sealed class DISCPowerShellConfiguration : PSSessionConfiguration { - private string _configFile; - private Hashtable _configHash; + private readonly string _configFile; + private readonly Hashtable _configHash; /// /// Gets the configuration hashtable that results from parsing the specified configuration file. @@ -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(); @@ -1896,18 +1928,18 @@ private void MergeConfigHashIntoConfigHash(IDictionary childConfigHash) } } - private string GetRoleCapabilityPath(string roleCapability) + 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 @@ -2221,7 +2253,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn foreach (Hashtable variable in variables) { if (variable.ContainsKey(ConfigFileConstants.VariableValueToken) && - ((variable[ConfigFileConstants.VariableValueToken] as ScriptBlock) != null)) + variable[ConfigFileConstants.VariableValueToken] is ScriptBlock) { iss.DynamicVariablesToDefine.Add(variable); continue; @@ -2395,7 +2427,7 @@ public override InitialSessionState GetInitialSessionState(PSSenderInfo senderIn if (Convert.ToBoolean(_configHash[ConfigFileConstants.MountUserDrive], CultureInfo.InvariantCulture)) { iss.UserDriveEnabled = true; - iss.UserDriveUserName = (senderInfo != null) ? senderInfo.UserInfo.Identity.Name : null; + iss.UserDriveUserName = senderInfo?.UserInfo.Identity.Name; // Set user drive max drive if provided. if (_configHash.ContainsKey(ConfigFileConstants.UserDriveMaxSize)) @@ -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); @@ -2620,7 +2652,7 @@ private static void ProcessVisibleCommand(InitialSessionState iss, string comman /// /// Creates an alias entry. /// - private SessionStateAliasEntry CreateSessionStateAliasEntry(Hashtable alias, bool isAliasVisibilityDefined) + private static SessionStateAliasEntry CreateSessionStateAliasEntry(Hashtable alias, bool isAliasVisibilityDefined) { string name = TryGetValue(alias, ConfigFileConstants.AliasNameToken); @@ -2660,7 +2692,7 @@ private SessionStateAliasEntry CreateSessionStateAliasEntry(Hashtable alias, boo /// Creates a function entry. /// /// - private SessionStateFunctionEntry CreateSessionStateFunctionEntry(Hashtable function, bool isFunctionVisibilityDefined) + private static SessionStateFunctionEntry CreateSessionStateFunctionEntry(Hashtable function, bool isFunctionVisibilityDefined) { string name = TryGetValue(function, ConfigFileConstants.FunctionNameToken); @@ -2700,7 +2732,7 @@ private SessionStateFunctionEntry CreateSessionStateFunctionEntry(Hashtable func /// /// Creates a variable entry. /// - private SessionStateVariableEntry CreateSessionStateVariableEntry(Hashtable variable, PSLanguageMode languageMode) + private static SessionStateVariableEntry CreateSessionStateVariableEntry(Hashtable variable, PSLanguageMode languageMode) { string name = TryGetValue(variable, ConfigFileConstants.VariableNameToken); @@ -2804,9 +2836,7 @@ internal static Hashtable[] TryGetHashtableArray(object hashObj) for (int i = 0; i < hashArray.Length; i++) { - Hashtable hash = objArray[i] as Hashtable; - - if (hash == null) + if (objArray[i] is not Hashtable hash) { return null; } @@ -2892,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 bc39b8f6937..47ff6270dba 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, @@ -144,12 +146,19 @@ private static string CreatePSGuidPacket(string element, Guid psGuid) #region Packet Processing Helper Methods / Delegates internal delegate void DataPacketReceived(byte[] rawData, string stream, Guid psGuid); + internal delegate void DataAckPacketReceived(Guid psGuid); + internal delegate void CommandCreationPacketReceived(Guid psGuid); + internal delegate void CommandCreationAckReceived(Guid psGuid); + internal delegate void ClosePacketReceived(Guid psGuid); + internal delegate void CloseAckPacketReceived(Guid psGuid); + internal delegate void SignalPacketReceived(Guid psGuid); + internal delegate void SignalAckPacketReceived(Guid psGuid); internal struct DataProcessingDelegates @@ -402,9 +411,22 @@ internal class OutOfProcessTextWriter { #region Private Data - private TextWriter _writer; + private readonly TextWriter _writer; private bool _isStopped; - private object _syncObject = new object(); + 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 @@ -414,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; } @@ -428,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) { @@ -460,25 +486,30 @@ 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 private readonly BlockingCollection _sessionMessageQueue; private readonly BlockingCollection _commandMessageQueue; - private PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; + private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; private OutOfProcessUtils.DataProcessingDelegates _dataProcessingCallbacks; - private Dictionary _cmdTransportManagers; - private Timer _closeTimeOutTimer; - - protected OutOfProcessTextWriter stdInWriter; - protected PowerShellTraceSource _tracer; + private readonly Dictionary _cmdTransportManagers; + private readonly Timer _closeTimeOutTimer; + internal PowerShellTraceSource _tracer; + internal OutOfProcessTextWriter _messageWriter; #endregion #region Constructor - internal OutOfProcessClientSessionTransportManagerBase( + /// + /// Constructor. + /// + protected ClientSessionTransportManagerBase( Guid runspaceId, PSRemotingCryptoHelper cryptoHelper) : base(runspaceId, cryptoHelper) @@ -498,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. @@ -539,7 +570,7 @@ internal override void ConnectAsync() /// /// Closes the server process. /// - internal override void CloseAsync() + public override void CloseAsync() { bool shouldRaiseCloseCompleted = false; lock (syncObject) @@ -553,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 @@ -579,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; @@ -611,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; @@ -621,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(); } } @@ -709,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) @@ -747,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)) @@ -778,6 +792,9 @@ protected void HandleOutputDataReceived(string data) } } + /// + /// Handles protocol error data. + /// protected void HandleErrorDataReceived(string data) { lock (syncObject) @@ -794,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; @@ -881,7 +854,7 @@ private void SendData(byte[] data, DataPriorityType priorityType) return; } - stdInWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, + _messageWriter.WriteLine(OutOfProcessUtils.CreateDataPacket(data, priorityType, Guid.Empty)); } @@ -924,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); } } @@ -945,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(); } } @@ -979,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); } @@ -988,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(); } } @@ -1028,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(); } } @@ -1048,14 +1014,76 @@ 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 private Process _serverProcess; - private NewProcessConnectionInfo _connectionInfo; + private readonly NewProcessConnectionInfo _connectionInfo; private bool _processCreated = true; private PowerShellProcessInstance _processInstance; @@ -1078,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) { @@ -1126,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) @@ -1237,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) @@ -1301,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 @@ -1326,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(); } } @@ -1404,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); @@ -1426,10 +1498,10 @@ internal sealed class VMHyperVSocketClientSessionTransportManager : HyperVSocket { #region Private Data - private Guid _vmGuid; - private string _configurationName; - private VMConnectionInfo _connectionInfo; - private NetworkCredential _networkCredential; + private readonly Guid _vmGuid; + private readonly string _configurationName; + private readonly VMConnectionInfo _connectionInfo; + private readonly NetworkCredential _networkCredential; #endregion @@ -1470,10 +1542,11 @@ 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)) + // isFirstConnection: true - specifies to use VM_SESSION_SERVICE_ID socket. + _client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: false, isFirstConnection: true); + if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: true)) { _client.Dispose(); throw new PSInvalidOperationException( @@ -1483,11 +1556,14 @@ internal override void CreateAsync() ErrorCategory.InvalidOperation, null); } + bool useBackwardsCompatibleMode = _client.UseBackwardsCompatibleMode; + string token = _client.AuthenticationToken; - // TODO: remove below 3 lines when Hyper-V socket duplication is supported in .NET framework. _client.Dispose(); - _client = new RemoteSessionHyperVSocketClient(_vmGuid, false); - if (!_client.Connect(_networkCredential, _configurationName, false)) + + // isFirstConnection: false - specifies to use the SESSION_SERVICE_ID_2 socket. + _client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: useBackwardsCompatibleMode, isFirstConnection: false, authenticationToken: token); + if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: false)) { _client.Dispose(); throw new PSInvalidOperationException( @@ -1499,7 +1575,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); @@ -1512,8 +1588,8 @@ internal sealed class ContainerHyperVSocketClientSessionTransportManager : Hyper { #region Private Data - private Guid _targetGuid; // currently this is the utility vm guid in HyperV container scenario - private ContainerConnectionInfo _connectionInfo; + private readonly Guid _targetGuid; // currently this is the utility vm guid in HyperV container scenario + private readonly ContainerConnectionInfo _connectionInfo; #endregion @@ -1543,9 +1619,11 @@ 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); + // Container scenario is not working. + // When we fix it we need to setup the token in ContainerConnectionInfo and use it here. + _client = new RemoteSessionHyperVSocketClient(_targetGuid, isFirstConnection: false, useBackwardsCompatibleMode: false, isContainer: true); if (!_client.Connect(null, string.Empty, false)) { _client.Dispose(); @@ -1558,7 +1636,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); @@ -1567,16 +1645,17 @@ internal override void CreateAsync() #endregion } - internal sealed class SSHClientSessionTransportManager : OutOfProcessClientSessionTransportManagerBase + internal sealed class SSHClientSessionTransportManager : ClientSessionTransportManagerBase { #region Data - private SSHConnectionInfo _connectionInfo; + private readonly SSHConnectionInfo _connectionInfo; private int _sshProcessId; private StreamWriter _stdInWriter; private StreamReader _stdOutReader; private StreamReader _stdErrReader; private bool _connectionEstablished; + private Timer _connectionTimer; private const string _threadName = "SSHTransport Reader Thread"; @@ -1599,7 +1678,7 @@ internal SSHClientSessionTransportManager( #region Overrides - internal override void Dispose(bool isDisposing) + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -1618,7 +1697,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( @@ -1630,13 +1709,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 = 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(); @@ -1653,14 +1775,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 @@ -1670,11 +1798,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) { } @@ -1701,7 +1825,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) { @@ -1709,12 +1842,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. + Console.WriteLine(error); + } + catch (IOException) + { } } } catch (ObjectDisposedException) @@ -1723,7 +1859,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); @@ -1740,55 +1876,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) { @@ -1820,10 +1907,10 @@ private void ProcessReaderThread(object state) break; } - if (data.StartsWith(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend, StringComparison.OrdinalIgnoreCase)) + if (data.StartsWith(OutOfProcessTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) { // Error message from the server. - string errorData = data.Substring(System.Management.Automation.Remoting.Server.NamedPipeErrorTextWriter.ErrorPrepend.Length); + string errorData = data.Substring(OutOfProcessTextWriter.ErrorPrefix.Length); HandleErrorDataReceived(errorData); } else @@ -1844,10 +1931,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); } @@ -1856,13 +1943,13 @@ private void ProcessReaderThread(object state) #endregion } - internal abstract class NamedPipeClientSessionTransportManagerBase : OutOfProcessClientSessionTransportManagerBase + internal abstract class NamedPipeClientSessionTransportManagerBase : ClientSessionTransportManagerBase { #region Data - private RunspaceConnectionInfo _connectionInfo; + private readonly RunspaceConnectionInfo _connectionInfo; protected NamedPipeClientBase _clientPipe = new NamedPipeClientBase(); - private string _threadName; + private readonly string _threadName; #endregion @@ -1889,16 +1976,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(); } } @@ -1950,17 +2034,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) @@ -1974,7 +2048,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); } @@ -1987,7 +2061,7 @@ internal sealed class NamedPipeClientSessionTransportManager : NamedPipeClientSe { #region Private Data - private NamedPipeConnectionInfo _connectionInfo; + private readonly NamedPipeConnectionInfo _connectionInfo; private const string _threadName = "NamedPipeTransport Reader Thread"; @@ -2017,7 +2091,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) : @@ -2027,7 +2101,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); @@ -2040,13 +2114,7 @@ internal override void CreateAsync() /// /// Aborts an existing connection attempt. /// - public void AbortConnect() - { - if (_clientPipe != null) - { - _clientPipe.AbortConnect(); - } - } + public void AbortConnect() => _clientPipe?.AbortConnect(); #endregion } @@ -2055,7 +2123,7 @@ internal sealed class ContainerNamedPipeClientSessionTransportManager : NamedPip { #region Private Data - private ContainerConnectionInfo _connectionInfo; + private readonly ContainerConnectionInfo _connectionInfo; private const string _threadName = "ContainerNamedPipeTransport Reader Thread"; @@ -2085,7 +2153,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, @@ -2096,7 +2164,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); @@ -2124,9 +2192,9 @@ internal class OutOfProcessClientCommandTransportManager : BaseClientCommandTran { #region Private Data - private OutOfProcessTextWriter _stdInWriter; - private PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; - private Timer _signalTimeOutTimer; + private readonly OutOfProcessTextWriter _stdInWriter; + private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; + private readonly Timer _signalTimeOutTimer; #endregion @@ -2135,7 +2203,7 @@ internal class OutOfProcessClientCommandTransportManager : BaseClientCommandTran internal OutOfProcessClientCommandTransportManager( ClientRemotePowerShell cmd, bool noInput, - OutOfProcessClientSessionTransportManagerBase sessnTM, + ClientSessionTransportManagerBase sessnTM, OutOfProcessTextWriter stdInWriter) : base(cmd, sessnTM.CryptoHelper, sessnTM) { _stdInWriter = stdInWriter; @@ -2153,7 +2221,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, @@ -2163,7 +2231,7 @@ internal override void CreateAsync() _stdInWriter.WriteLine(OutOfProcessUtils.CreateCommandPacket(powershellInstanceId)); } - internal override void CloseAsync() + public override void CloseAsync() { lock (syncObject) { @@ -2217,7 +2285,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) @@ -2425,10 +2493,10 @@ internal class OutOfProcessServerSessionTransportManager : AbstractServerSession { #region Private Data - private OutOfProcessTextWriter _stdOutWriter; - private OutOfProcessTextWriter _stdErrWriter; - private Dictionary _cmdTransportManagers; - private object _syncObject = new object(); + private readonly OutOfProcessTextWriter _stdOutWriter; + private readonly OutOfProcessTextWriter _stdErrWriter; + private readonly Dictionary _cmdTransportManagers; + private readonly object _syncObject = new object(); #endregion @@ -2442,6 +2510,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 @@ -2523,9 +2597,9 @@ internal class OutOfProcessServerTransportManager : AbstractServerTransportManag { #region Private Data - private OutOfProcessTextWriter _stdOutWriter; - private OutOfProcessTextWriter _stdErrWriter; - private Guid _powershellInstanceId; + private readonly OutOfProcessTextWriter _stdOutWriter; + private readonly OutOfProcessTextWriter _stdErrWriter; + private readonly Guid _powershellInstanceId; private bool _isDataAckSendPending; #endregion @@ -2604,4 +2678,3 @@ internal override void Close(Exception reasonForClose) #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 17aac7adfdf..1d5f6916981 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PSSessionConfigurationData.cs @@ -18,7 +18,9 @@ public sealed class PSSessionConfigurationData { /// /// +#pragma warning disable CA2211 // Non-constant fields should not be visible public static bool IsServerManager; +#pragma warning restore CA2211 // Non-constant fields should not be visible #region Public Properties @@ -78,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); @@ -222,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 6c2b64479f4..a0f38a78a07 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -55,9 +55,9 @@ internal class PrioritySendDataCollection // these callbacks are used to notify when data becomes available under // suc circumstances. private OnDataAvailableCallback _onDataAvailableCallback; - private SerializedDataStream.OnDataAvailableCallback _onSendCollectionDataAvailable; + private readonly SerializedDataStream.OnDataAvailableCallback _onSendCollectionDataAvailable; private bool _isHandlingCallback; - private object _readSyncObject = new object(); + private readonly object _readSyncObject = new object(); /// /// Callback that is called once a fragmented data is available to send. @@ -88,14 +88,17 @@ internal PrioritySendDataCollection() internal Fragmentor Fragmentor { - get { return _fragmentor; } + get + { + return _fragmentor; + } set { 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++) @@ -163,8 +166,8 @@ Verify arrays and dataToBeSent objects before referencing. */ if (_dataSyncObjects != null && _dataToBeSent != null) { - int promptResponseIndex = (int)DataPriorityType.PromptResponse; - int defaultIndex = (int)DataPriorityType.Default; + const int promptResponseIndex = (int)DataPriorityType.PromptResponse; + const int defaultIndex = (int)DataPriorityType.Default; lock (_dataSyncObjects[promptResponseIndex]) { @@ -217,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; } @@ -290,14 +301,14 @@ internal class ReceiveDataCollection : IDisposable { #region tracer - [TraceSourceAttribute("Transport", "Traces BaseWSManTransportManager")] - private static PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); + [TraceSource("Transport", "Traces BaseWSManTransportManager")] + private static readonly PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); #endregion #region Private Data // fragmentor used to defragment objects added to this collection. - private Fragmentor _defragmentor; + private readonly Fragmentor _defragmentor; // this stream holds incoming data..this stream doesn't know anything // about fragment boundaries. @@ -311,7 +322,7 @@ internal class ReceiveDataCollection : IDisposable // max deserialized object size in bytes private int? _maxReceivedObjectSize; private int _totalReceivedObjectSizeSoFar; - private bool _isCreateByClientTM; + private readonly bool _isCreateByClientTM; // this indicates if any off sync fragments can be ignored // this gets reset (to false) upon receiving the next "start" fragment along the stream @@ -319,7 +330,7 @@ internal class ReceiveDataCollection : IDisposable // objects need to cleanly release resources without // locking entire processing logic. - private object _syncObject; + private readonly object _syncObject; private bool _isDisposed; // holds the number of threads that are currently in // ProcessRawData method. This might happen only for @@ -455,7 +466,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) // this do loop will process one deserialized object. // using a loop allows to process multiple objects within // the same packet - do + while (true) { if (_pendingDataStream.Length <= FragmentedRemoteObject.HeaderLength) { @@ -550,11 +561,11 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) PSEtwLog.LogAnalyticVerbose( PSEventId.ReceivedRemotingFragment, PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - (Int64)objectId, - (Int64)fragmentId, + (long)objectId, + (long)fragmentId, sFlag ? 1 : 0, eFlag ? 1 : 0, - (UInt32)blobLength, + (uint)blobLength, new PSETWBinaryBlob(oneFragment, FragmentedRemoteObject.HeaderLength, blobLength)); byte[] extraData = null; @@ -651,7 +662,7 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) break; } } - } while (true); + } } finally { @@ -673,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; @@ -738,9 +746,9 @@ internal class PriorityReceiveDataCollection : IDisposable { #region Private Data - private Fragmentor _defragmentor; - private ReceiveDataCollection[] _recvdData; - private bool _isCreateByClientTM; + private readonly Fragmentor _defragmentor; + private readonly ReceiveDataCollection[] _recvdData; + private readonly bool _isCreateByClientTM; #endregion @@ -757,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 1b1fe2b2163..d7bce634620 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs @@ -192,7 +192,7 @@ internal static MarshalledObject Create(T obj) /// public void Dispose() { - if (IntPtr.Zero != _dataPtr) + if (_dataPtr != IntPtr.Zero) { Marshal.FreeHGlobal(_dataPtr); _dataPtr = IntPtr.Zero; @@ -304,7 +304,6 @@ internal struct WSManUserNameCredentialStruct /// /// Making password secure. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr password; } @@ -612,7 +611,7 @@ internal enum WSManDataType : uint WSMAN_DATA_TYPE_BINARY = 2, WSMAN_DATA_TYPE_WS_XML_READER = 3, WSMAN_DATA_TYPE_DWORD = 4 - }; + } [StructLayout(LayoutKind.Sequential)] internal class WSManDataStruct @@ -626,7 +625,6 @@ internal class WSManBinaryOrTextDataStruct { internal int bufferLength; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr data; } @@ -635,12 +633,10 @@ internal class WSManBinaryOrTextDataStruct /// internal class WSManData_ManToUn : IDisposable { - private WSManDataStruct _internalData; + private readonly WSManDataStruct _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledObject = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledBuffer = IntPtr.Zero; /// @@ -687,18 +683,6 @@ internal WSManData_ManToUn(string data) Marshal.StructureToPtr(_internalData, _marshalledObject, false); } - /// - /// Finalizer - /// - /// Note: Do not depend on the finalizer! This object should be - /// properly disposed of when no longer needed via a direct call - /// to Dispose(). - /// - ~WSManData_ManToUn() - { - Dispose(false); - } - /// /// Gets the type of data. /// @@ -747,6 +731,14 @@ private void Dispose(bool isDisposing) } } + /// + /// Finalizes an instance of the class. + /// + ~WSManData_ManToUn() + { + Dispose(false); + } + /// /// Implicit IntPtr conversion. /// @@ -869,7 +861,7 @@ internal static WSManData_UnToMan UnMarshal(IntPtr unmanagedData) { WSManData_UnToMan result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManDataStruct resultInternal = Marshal.PtrToStructure(unmanagedData); result = WSManData_UnToMan.UnMarshal(resultInternal); @@ -885,7 +877,7 @@ internal static WSManData_UnToMan UnMarshal(IntPtr unmanagedData) [StructLayout(LayoutKind.Sequential)] internal struct WSManDataDWord { - private WSManDataType _type; + private readonly WSManDataType _type; private WSManDWordDataInternal _dwordData; /// @@ -937,7 +929,6 @@ internal struct WSManStreamIDSetStruct { internal int streamIDsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr streamIDs; } @@ -972,7 +963,7 @@ internal WSManStreamIDSet_ManToUn(string[] streamIds) /// internal void Dispose() { - if (IntPtr.Zero != _streamSetInfo.streamIDs) + if (_streamSetInfo.streamIDs != IntPtr.Zero) { int sizeOfIntPtr = Marshal.SizeOf(); for (int index = 0; index < _streamSetInfo.streamIDsCount; index++) @@ -980,7 +971,7 @@ internal void Dispose() IntPtr streamAddress = IntPtr.Zero; streamAddress = Marshal.ReadIntPtr(_streamSetInfo.streamIDs, index * sizeOfIntPtr); - if (IntPtr.Zero != streamAddress) + if (streamAddress != IntPtr.Zero) { Marshal.FreeHGlobal(streamAddress); streamAddress = IntPtr.Zero; @@ -1019,7 +1010,7 @@ internal static WSManStreamIDSet_UnToMan UnMarshal(IntPtr unmanagedData) { WSManStreamIDSet_UnToMan result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManStreamIDSetStruct resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -1089,7 +1080,6 @@ internal struct WSManOptionSetStruct /// /// Pointer to an array of WSManOption objects. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr options; internal bool optionsMustUnderstand; @@ -1140,7 +1130,7 @@ internal WSManOptionSet(WSManOption[] options) /// public void Dispose() { - if (IntPtr.Zero != _optionSet.options) + if (_optionSet.options != IntPtr.Zero) { Marshal.FreeHGlobal(_optionSet.options); _optionSet.options = IntPtr.Zero; @@ -1174,7 +1164,7 @@ public static implicit operator IntPtr(WSManOptionSet optionSet) /// internal static WSManOptionSet UnMarshal(IntPtr unmanagedData) { - if (IntPtr.Zero == unmanagedData) + if (unmanagedData == IntPtr.Zero) { return new WSManOptionSet(); } @@ -1227,13 +1217,11 @@ internal struct WSManCommandArgSetInternal { internal int argsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr args; } private WSManCommandArgSetInternal _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private MarshalledObject _data; #region Managed to Unmanaged @@ -1264,7 +1252,7 @@ internal WSManCommandArgSet(byte[] firstArgument) public void Dispose() { IntPtr firstArgAddress = Marshal.ReadIntPtr(_internalData.args); - if (IntPtr.Zero != firstArgAddress) + if (firstArgAddress != IntPtr.Zero) { Marshal.FreeHGlobal(firstArgAddress); } @@ -1302,7 +1290,7 @@ internal static WSManCommandArgSet UnMarshal(IntPtr unmanagedData) { WSManCommandArgSet result = new WSManCommandArgSet(); - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManCommandArgSetInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -1408,7 +1396,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 { @@ -1486,7 +1474,7 @@ internal static WSManShellStartupInfo_UnToMan UnMarshal(IntPtr unmanagedData) { WSManShellStartupInfo_UnToMan result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManShellStartupInfoStruct resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -1522,7 +1510,7 @@ internal static WSManEnvironmentVariableSet UnMarshal(IntPtr unmanagedData) { WSManEnvironmentVariableSet result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManEnvironmentVariableSetInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -1737,8 +1725,7 @@ internal struct WSManShellAsyncCallback // GC handle which prevents garbage collector from collecting this delegate. private GCHandle _gcHandle; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] - private IntPtr _asyncCallback; + private readonly IntPtr _asyncCallback; internal WSManShellAsyncCallback(WSManShellCompletionFunction callback) { @@ -1841,7 +1828,7 @@ internal static WSManCreateShellDataResult UnMarshal(IntPtr unmanagedData) { WSManCreateShellDataResult result = new WSManCreateShellDataResult(); - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManCreateShellDataResultInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -2010,13 +1997,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; @@ -2058,7 +2045,7 @@ internal static WSManPluginRequest UnMarshal(IntPtr unmanagedData) // Dbg.Assert(IntPtr.Zero != unmanagedData, "unmanagedData must be non-null. This means WinRM sent a bad pointer."); WSManPluginRequest result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManPluginRequestInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -2104,7 +2091,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; /// @@ -2116,7 +2103,7 @@ internal static WSManSenderDetails UnMarshal(IntPtr unmanagedData) { WSManSenderDetails result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManSenderDetailsInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -2169,7 +2156,7 @@ internal static WSManCertificateDetails UnMarshal(IntPtr unmanagedData) { WSManCertificateDetails result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManCertificateDetailsInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -2218,7 +2205,7 @@ internal static WSManOperationInfo UnMarshal(IntPtr unmanagedData) { WSManOperationInfo result = null; - if (IntPtr.Zero != unmanagedData) + if (unmanagedData != IntPtr.Zero) { WSManOperationInfoInternal resultInternal = Marshal.PtrToStructure(unmanagedData); @@ -2357,7 +2344,7 @@ internal struct WSManKeyStruct /// [DllImport(WSManNativeApi.WSManClientApiDll, SetLastError = false, CharSet = CharSet.Unicode)] internal static extern int WSManInitialize(int flags, - [In, Out] ref IntPtr wsManAPIHandle); + [In, Out] ref IntPtr wsManAPIHandle); /// /// This API deinitializes the Winrm client stack; all operations will @@ -2390,11 +2377,11 @@ internal static extern int WSManInitialize(int flags, /// [DllImport(WSManNativeApi.WSManClientApiDll, SetLastError = false, CharSet = CharSet.Unicode)] internal static extern int WSManCreateSession(IntPtr wsManAPIHandle, - [MarshalAs(UnmanagedType.LPWStr)]string connection, + [MarshalAs(UnmanagedType.LPWStr)] string connection, int flags, IntPtr authenticationCredentials, IntPtr proxyInfo, - [In, Out] ref IntPtr wsManSessionHandle); + [In, Out] ref IntPtr wsManSessionHandle); /// /// Frees memory of session and closes all related operations before returning; @@ -2443,9 +2430,9 @@ internal static extern int WSManSetSessionOption(IntPtr wsManSessionHandle, /// /// An int (DWORD) data. /// - /// + /// Zero on success, otherwise the error code. [DllImport(WSManNativeApi.WSManClientApiDll, SetLastError = false, CharSet = CharSet.Unicode)] - internal static extern void WSManGetSessionOptionAsDword(IntPtr wsManSessionHandle, + internal static extern int WSManGetSessionOptionAsDword(IntPtr wsManSessionHandle, WSManSessionOption option, out int value); @@ -2459,15 +2446,15 @@ internal static extern void WSManGetSessionOptionAsDword(IntPtr wsManSessionHand internal static string WSManGetSessionOptionAsString(IntPtr wsManAPIHandle, WSManSessionOption option) { - Dbg.Assert(IntPtr.Zero != wsManAPIHandle, "wsManAPIHandle cannot be null."); + Dbg.Assert(wsManAPIHandle != IntPtr.Zero, "wsManAPIHandle cannot be null."); // The error code taken from winerror.h used for getting buffer length. const int ERROR_INSUFFICIENT_BUFFER = 122; string returnval = string.Empty; int bufferSize = 0; // calculate buffer size required - if (ERROR_INSUFFICIENT_BUFFER != WSManGetSessionOptionAsString(wsManAPIHandle, - option, 0, null, out bufferSize)) + if (WSManGetSessionOptionAsString(wsManAPIHandle, + option, 0, null, out bufferSize) != ERROR_INSUFFICIENT_BUFFER) { return returnval; } @@ -2480,8 +2467,8 @@ internal static string WSManGetSessionOptionAsString(IntPtr wsManAPIHandle, // Now get the actual value int messageLength; - if (0 != WSManGetSessionOptionAsString(wsManAPIHandle, - option, bufferSizeInBytes, msgBufferPtr, out messageLength)) + if (WSManGetSessionOptionAsString(wsManAPIHandle, + option, bufferSizeInBytes, msgBufferPtr, out messageLength) != 0) { return returnval; } @@ -2553,13 +2540,13 @@ internal static void WSManCreateShellEx(IntPtr wsManSessionHandle, [DllImport(WSManNativeApi.WSManClientApiDll, EntryPoint = "WSManCreateShellEx", SetLastError = false, CharSet = CharSet.Unicode)] private static extern void WSManCreateShellExInternal(IntPtr wsManSessionHandle, int flags, - [MarshalAs(UnmanagedType.LPWStr)]string resourceUri, - [MarshalAs(UnmanagedType.LPWStr)]string shellId, + [MarshalAs(UnmanagedType.LPWStr)] string resourceUri, + [MarshalAs(UnmanagedType.LPWStr)] string shellId, IntPtr startupInfo, IntPtr optionSet, IntPtr openContent, IntPtr asyncCallback, - [In, Out] ref IntPtr shellOperationHandle); + [In, Out] ref IntPtr shellOperationHandle); /// /// @@ -2574,12 +2561,12 @@ private static extern void WSManCreateShellExInternal(IntPtr wsManSessionHandle, [DllImport(WSManNativeApi.WSManClientApiDll, EntryPoint = "WSManConnectShell", SetLastError = false, CharSet = CharSet.Unicode)] internal static extern void WSManConnectShellEx(IntPtr wsManSessionHandle, int flags, - [MarshalAs(UnmanagedType.LPWStr)]string resourceUri, - [MarshalAs(UnmanagedType.LPWStr)]string shellId, + [MarshalAs(UnmanagedType.LPWStr)] string resourceUri, + [MarshalAs(UnmanagedType.LPWStr)] string shellId, IntPtr optionSet, IntPtr connectXml, IntPtr asyncCallback, - [In, Out] ref IntPtr shellOperationHandle); + [In, Out] ref IntPtr shellOperationHandle); /// /// @@ -2685,7 +2672,7 @@ internal static extern void WSManReceiveShellOutputEx(IntPtr shellOperationHandl int flags, IntPtr desiredStreamSet, IntPtr asyncCallback, - [In, Out] ref IntPtr receiveOperationHandle); + [In, Out] ref IntPtr receiveOperationHandle); /// /// Send data to the remote end. @@ -2726,7 +2713,7 @@ private static extern void WSManSendShellInputExInternal(IntPtr shellOperationHa IntPtr streamData, bool endOfStream, IntPtr asyncCallback, - [In, Out] ref IntPtr sendOperationHandle); + [In, Out] ref IntPtr sendOperationHandle); /// /// Closes a shell or a command; if the callback associated with the operation @@ -2805,7 +2792,7 @@ internal static extern void WSManSignalShellEx(IntPtr shellOperationHandle, /// internal static string WSManGetErrorMessage(IntPtr wsManAPIHandle, int errorCode) { - Dbg.Assert(IntPtr.Zero != wsManAPIHandle, "wsManAPIHandle cannot be null."); + Dbg.Assert(wsManAPIHandle != IntPtr.Zero, "wsManAPIHandle cannot be null."); // The error code taken from winerror.h used for getting buffer length. const int ERROR_INSUFFICIENT_BUFFER = 122; @@ -2816,8 +2803,8 @@ internal static string WSManGetErrorMessage(IntPtr wsManAPIHandle, int errorCode string returnval = string.Empty; int bufferSize = 0; // calculate buffer size required - if (ERROR_INSUFFICIENT_BUFFER != WSManGetErrorMessage(wsManAPIHandle, - 0, langCode, errorCode, 0, null, out bufferSize)) + if (WSManGetErrorMessage(wsManAPIHandle, + 0, langCode, errorCode, 0, null, out bufferSize) != ERROR_INSUFFICIENT_BUFFER) { return returnval; } @@ -2830,8 +2817,8 @@ internal static string WSManGetErrorMessage(IntPtr wsManAPIHandle, int errorCode // Now get the actual value int messageLength; - if (0 != WSManGetErrorMessage(wsManAPIHandle, - 0, langCode, errorCode, bufferSizeInBytes, msgBufferPtr, out messageLength)) + if (WSManGetErrorMessage(wsManAPIHandle, + 0, langCode, errorCode, bufferSizeInBytes, msgBufferPtr, out messageLength) != 0) { return returnval; } @@ -3019,6 +3006,7 @@ internal static extern void WSManPluginRegisterShutdownCallback( /// unit testing. /// Note: It is implemented as a class to avoid exposing it outside the module. /// +#nullable enable internal interface IWSManNativeApiFacade { // TODO: Expand this to cover the rest of the API once I prove that it works! @@ -3037,7 +3025,7 @@ int WSManPluginOperationComplete( int WSManPluginReceiveResult( IntPtr requestDetails, int flags, - string stream, + string? stream, IntPtr streamResult, string commandState, int exitCode); @@ -3052,6 +3040,7 @@ void WSManPluginRegisterShutdownCallback( IntPtr shutdownCallback, IntPtr shutdownContext); } +#nullable restore /// /// Concrete implementation of the PInvoke facade for use in the production code. diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs index d5eb990ec57..79d583e063f 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPlugin.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + // ---------------------------------------------------------------------- // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. @@ -127,9 +128,9 @@ internal class WSManPluginInstance { #region Private Members - private Dictionary _activeShellSessions; - private object _syncObject; - private static Dictionary s_activePlugins = new Dictionary(); + private readonly Dictionary _activeShellSessions; + private readonly object _syncObject; + private static readonly Dictionary s_activePlugins = new Dictionary(); /// /// Enables dependency injection after the static constructor is called. @@ -246,7 +247,7 @@ internal void CreateShell( "CreateShell: NULL checks being performed", string.Empty); - if ((0 == startupInfo.inputStreamSet.streamIDsCount) || (0 == startupInfo.outputStreamSet.streamIDsCount)) + if ((startupInfo.inputStreamSet.streamIDsCount == 0) || (startupInfo.outputStreamSet.streamIDsCount == 0)) { ReportOperationComplete( requestDetails, @@ -305,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) { @@ -332,7 +339,7 @@ internal void CreateShell( if (inboundShellInformation != null) { - if ((uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT != inboundShellInformation.Type) + if (inboundShellInformation.Type != (uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT) { // only text data is supported ReportOperationComplete( @@ -360,7 +367,7 @@ internal void CreateShell( requestDetails.ToString(), requestDetails.ToString()); result = wsmanPinvokeStatic.WSManPluginReportContext(requestDetails.unmanagedHandle, 0, requestDetails.unmanagedHandle); - if (WSManPluginConstants.ExitCodeSuccess != result) + if (result != WSManPluginConstants.ExitCodeSuccess) { ReportOperationComplete( requestDetails, @@ -562,7 +569,7 @@ private void AddToActiveShellSessions( lock (_syncObject) { IntPtr key = newShellSession.creationRequestDetails.unmanagedHandle; - Dbg.Assert(IntPtr.Zero != key, "NULL handles should not be provided"); + Dbg.Assert(key != IntPtr.Zero, "NULL handles should not be provided"); if (!_activeShellSessions.ContainsKey(key)) { @@ -573,7 +580,7 @@ private void AddToActiveShellSessions( } } - if (-1 != count) + if (count != -1) { // Raise session count changed event WSManServerChannelEvents.RaiseActiveSessionsChangedEvent(new ActiveSessionsChangedEventArgs(count)); @@ -613,7 +620,7 @@ private void DeleteFromActiveShellSessions( } } - if (-1 != count) + if (count != -1) { // Raise session count changed event WSManServerChannelEvents.RaiseActiveSessionsChangedEvent(new ActiveSessionsChangedEventArgs(count)); @@ -639,7 +646,7 @@ private void HandleShellSessionClosed( /// /// /// - private bool validateIncomingContexts( + private static bool validateIncomingContexts( WSManNativeApi.WSManPluginRequest requestDetails, IntPtr shellContext, string inputFunctionName) @@ -661,7 +668,7 @@ private bool validateIncomingContexts( return false; } - if (IntPtr.Zero == shellContext) + if (shellContext == IntPtr.Zero) { ReportOperationComplete( requestDetails, @@ -870,7 +877,7 @@ internal void ConnectShellOrCommand( return; } - if (IntPtr.Zero == commandContext) + if (commandContext == IntPtr.Zero) { mgdShellSession.ExecuteConnect(requestDetails, flags, inboundConnectInformation); return; @@ -963,7 +970,7 @@ internal void SendOneItemToShellOrCommand( return; } - if (IntPtr.Zero == commandContext) + if (commandContext == IntPtr.Zero) { // the data is destined for shell (runspace) session. so let shell handle it mgdShellSession.SendOneItemToSession(requestDetails, flags, stream, inboundData); @@ -1074,7 +1081,7 @@ internal void EnableShellOrCommandToSendDataToClient( "EnableShellOrCommandToSendDataToClient: Instruction destined to shell or for command", string.Empty); - if (IntPtr.Zero == commandContext) + if (commandContext == IntPtr.Zero) { // the instruction is destined for shell (runspace) session. so let shell handle it if (mgdShellSession.EnableSessionToSendDataToClient(requestDetails, flags, streamSet, ctxtToReport)) @@ -1110,7 +1117,7 @@ internal void EnableShellOrCommandToSendDataToClient( /// /// /// - private PSSenderInfo GetPSSenderInfo( + private static PSSenderInfo GetPSSenderInfo( WSManNativeApi.WSManSenderDetails senderDetails) { // senderDetails will not be null. @@ -1167,7 +1174,7 @@ private PSSenderInfo GetPSSenderInfo( /// environment variable, which is set in the WSMan layer for Virtual or RunAs accounts. /// /// ClientToken IntPtr. - private IntPtr GetRunAsClientToken() + private static IntPtr GetRunAsClientToken() { string clientTokenStr = System.Environment.GetEnvironmentVariable(WSManRunAsClientTokenName); if (clientTokenStr != null) @@ -1210,7 +1217,7 @@ protected internal bool EnsureOptionsComply( isProtocolVersionDeclared = true; } - if (0 == string.Compare(option.name, 0, WSManPluginConstants.PowerShellOptionPrefix, 0, WSManPluginConstants.PowerShellOptionPrefix.Length, StringComparison.Ordinal)) + if (string.Compare(option.name, 0, WSManPluginConstants.PowerShellOptionPrefix, 0, WSManPluginConstants.PowerShellOptionPrefix.Length, StringComparison.Ordinal) == 0) { if (option.mustComply) { @@ -1575,7 +1582,7 @@ internal static void PerformWSManPluginSignal( WSManNativeApi.WSManPluginRequest request = WSManNativeApi.WSManPluginRequest.UnMarshal(requestDetails); // Close Command - if (IntPtr.Zero != commandContext) + if (commandContext != IntPtr.Zero) { if (!string.Equals(code, WSManPluginConstants.CtrlCSignal, StringComparison.Ordinal)) { @@ -1639,7 +1646,7 @@ internal static void PerformCloseOperation( return; } - if (IntPtr.Zero == context.commandContext) + if (context.commandContext == IntPtr.Zero) { // this is targeted at shell pluginToUse.CloseShellOperation(context); @@ -1784,7 +1791,7 @@ internal static void SetThreadProperties( WSManPluginConstants.WSManPluginParamsGetRequestedLocale, outputStruct); // ref nativeLocaleData); - bool retrievingLocaleSucceeded = (0 == hResult); + bool retrievingLocaleSucceeded = (hResult == 0); WSManNativeApi.WSManData_UnToMan localeData = WSManNativeApi.WSManData_UnToMan.UnMarshal(outputStruct); // nativeLocaleData // IntPtr nativeDataLocaleData = IntPtr.Zero; @@ -1793,13 +1800,13 @@ internal static void SetThreadProperties( WSManPluginConstants.WSManPluginParamsGetRequestedDataLocale, outputStruct); // ref nativeDataLocaleData); - bool retrievingDataLocaleSucceeded = ((int)WSManPluginErrorCodes.NoError == hResult); + bool retrievingDataLocaleSucceeded = (hResult == (int)WSManPluginErrorCodes.NoError); WSManNativeApi.WSManData_UnToMan dataLocaleData = WSManNativeApi.WSManData_UnToMan.UnMarshal(outputStruct); // nativeDataLocaleData // Set the UI Culture try { - if (retrievingLocaleSucceeded && ((uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT == localeData.Type)) + if (retrievingLocaleSucceeded && (localeData.Type == (uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT)) { CultureInfo uiCultureToUse = new CultureInfo(localeData.Text); Thread.CurrentThread.CurrentUICulture = uiCultureToUse; @@ -1813,7 +1820,7 @@ internal static void SetThreadProperties( // Set the Culture try { - if (retrievingDataLocaleSucceeded && ((uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT == dataLocaleData.Type)) + if (retrievingDataLocaleSucceeded && (dataLocaleData.Type == (uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_TEXT)) { CultureInfo cultureToUse = new CultureInfo(dataLocaleData.Text); Thread.CurrentThread.CurrentCulture = cultureToUse; @@ -1882,7 +1889,7 @@ internal static void ReportOperationComplete( WSManPluginErrorCodes errorCode) { if (requestDetails != null && - IntPtr.Zero != requestDetails.unmanagedHandle) + requestDetails.unmanagedHandle != IntPtr.Zero) { wsmanPinvokeStatic.WSManPluginOperationComplete( requestDetails.unmanagedHandle, @@ -1905,7 +1912,7 @@ internal static void ReportOperationComplete( WSManPluginErrorCodes errorCode, string errorMessage = "") { - if (IntPtr.Zero == requestDetails) + if (requestDetails == IntPtr.Zero) { // cannot report if requestDetails is null. return; diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs index ed5bf0a4a3e..2d99a42cfb9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + // ---------------------------------------------------------------------- // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. @@ -32,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, @@ -44,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); @@ -56,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, @@ -72,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, @@ -84,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); /// @@ -92,7 +93,7 @@ internal delegate void WSMPluginOperationShutdownDelegate( /// PVOID. /// PVOID. /// PVOID. - internal delegate void WSMPluginReleaseCommandContextDelegate( + internal delegate void WSManPluginReleaseCommandContextDelegate( IntPtr pluginContext, IntPtr shellContext, IntPtr commandContext); @@ -106,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, @@ -123,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, @@ -139,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, @@ -159,7 +160,7 @@ internal delegate void WaitOrTimerCallbackDelegate( /// /// /// PVOID. - internal delegate void WSMShutdownPluginDelegate( + internal delegate void WSManShutdownPluginDelegate( IntPtr pluginContext); /// @@ -169,7 +170,7 @@ internal sealed class WSManPluginEntryDelegates : IDisposable #region Private Members // Holds the delegate pointers in a structure that has identical layout to the native structure. - private WSManPluginEntryDelegatesInternal _unmanagedStruct = new WSManPluginEntryDelegatesInternal(); + private readonly WSManPluginEntryDelegatesInternal _unmanagedStruct = new WSManPluginEntryDelegatesInternal(); internal WSManPluginEntryDelegatesInternal UnmanagedStruct { @@ -191,7 +192,7 @@ internal WSManPluginEntryDelegatesInternal UnmanagedStruct private GCHandle _pluginSignalGCHandle; private GCHandle _pluginConnectGCHandle; private GCHandle _shutdownPluginGCHandle; - private GCHandle _WSMPluginOperationShutdownGCHandle; + private GCHandle _WSManPluginOperationShutdownGCHandle; #endregion @@ -233,11 +234,7 @@ private void Dispose(bool disposing) } /// - /// Use C# destructor syntax for finalization code. - /// This destructor will run only if the Dispose method - /// does not get called. - /// It gives your base class the opportunity to finalize. - /// Do not provide destructors in types derived from this class. + /// Finalizes an instance of the class. /// ~WSManPluginEntryDelegates() { @@ -258,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); } } @@ -318,7 +315,7 @@ private void populateDelegates() private void CleanUpDelegates() { // Free GCHandles so that the memory they point to may be unpinned (garbage collected) - if (_pluginShellGCHandle != null) + if (_pluginShellGCHandle.IsAllocated) { _pluginShellGCHandle.Free(); _pluginReleaseShellContextGCHandle.Free(); @@ -331,7 +328,7 @@ private void CleanUpDelegates() _shutdownPluginGCHandle.Free(); if (!Platform.IsWindows) { - _WSMPluginOperationShutdownGCHandle.Free(); + _WSManPluginOperationShutdownGCHandle.Free(); } } } @@ -346,61 +343,51 @@ internal class WSManPluginEntryDelegatesInternal /// /// WsManPluginShutdownPluginCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownPluginCallbackNative; /// /// WSManPluginShellCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShellCallbackNative; /// /// WSManPluginReleaseShellContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseShellContextCallbackNative; /// /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginCommandCallbackNative; /// /// WSManPluginReleaseCommandContextCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseCommandContextCallbackNative; /// /// WSManPluginSendCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSendCallbackNative; /// /// WSManPluginReceiveCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReceiveCallbackNative; /// /// WSManPluginSignalCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSignalCallbackNative; /// /// WSManPluginConnectCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginConnectCallbackNative; /// /// WSManPluginCommandCallbackNative. /// - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownCallbackNative; } } @@ -432,7 +419,7 @@ private WSManPluginManagedEntryWrapper() { } public static int InitPlugin( IntPtr wkrPtrs) { - if (IntPtr.Zero == wkrPtrs) + if (wkrPtrs == IntPtr.Zero) { return WSManPluginConstants.ExitCodeFailure; } @@ -449,11 +436,7 @@ public static void ShutdownPlugin( IntPtr pluginContext) { WSManPluginInstance.PerformShutdown(pluginContext); - - if (workerPtrs != null) - { - workerPtrs.Dispose(); - } + workerPtrs?.Dispose(); } /// @@ -472,7 +455,7 @@ public static void WSManPluginConnect( IntPtr commandContext, IntPtr inboundConnectInformation) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -504,7 +487,7 @@ public static void WSManPluginShell( IntPtr startupInfo, IntPtr inboundShellInformation) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -560,7 +543,7 @@ public static void WSManPluginCommand( [MarshalAs(UnmanagedType.LPWStr)] string commandLine, IntPtr arguments) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -621,7 +604,7 @@ public static void WSManPluginSend( [MarshalAs(UnmanagedType.LPWStr)] string stream, IntPtr inboundData) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -653,7 +636,7 @@ public static void WSManPluginReceive( IntPtr commandContext, IntPtr streamSet) { - if (IntPtr.Zero == pluginContext) + if (pluginContext == IntPtr.Zero) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -685,7 +668,7 @@ public static void WSManPluginSignal( IntPtr commandContext, [MarshalAs(UnmanagedType.LPWStr)] string code) { - if ((IntPtr.Zero == pluginContext) || (IntPtr.Zero == shellContext)) + if ((pluginContext == IntPtr.Zero) || (shellContext == IntPtr.Zero)) { WSManPluginInstance.ReportOperationComplete( requestDetails, @@ -765,11 +748,7 @@ private void Dispose(bool disposing) } /// - /// Use C# destructor syntax for finalization code. - /// This destructor will run only if the Dispose method - /// does not get called. - /// It gives your base class the opportunity to finalize. - /// Do not provide destructors in types derived from this class. + /// Finalizes an instance of the class. /// ~WSManPluginManagedEntryInstanceWrapper() { diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs index f4dabc11558..54a7b66d0c1 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginShellSession.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + // ---------------------------------------------------------------------- // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. @@ -24,7 +25,7 @@ namespace System.Management.Automation.Remoting /// internal abstract class WSManPluginServerSession : IDisposable { - private object _syncObject; + private readonly object _syncObject; protected bool isClosed; protected bool isContextReported; @@ -110,11 +111,7 @@ protected virtual void Dispose(bool disposing) } /// - /// Use C# destructor syntax for finalization code. - /// This destructor will run only if the Dispose method - /// does not get called. - /// It gives your base class the opportunity to finalize. - /// Do not provide destructors in types derived from this class. + /// Finalizes an instance of the class. /// ~WSManPluginServerSession() { @@ -151,7 +148,7 @@ internal void SendOneItemToSession( return; } - if ((uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_BINARY != inboundData.Type) + if (inboundData.Type != (uint)WSManNativeApi.WSManDataType.WSMAN_DATA_TYPE_BINARY) { // only binary data is supported WSManPluginInstance.ReportOperationComplete( @@ -201,7 +198,7 @@ internal bool EnableSessionToSendDataToClient( } if ((streamSet == null) || - (1 != streamSet.streamIDsCount)) + (streamSet.streamIDsCount != 1)) { // only "stdout" is the supported output stream. WSManPluginInstance.ReportOperationComplete( @@ -255,7 +252,7 @@ internal void ReportContext() // TO BE FIXED - As soon as this API is called, WinRM service will send CommandResponse back and Signal is expected anytime // If Signal comes and executes before registering the notification handle, cleanup will be messed result = WSManNativeApi.WSManPluginReportContext(creationRequestDetails.unmanagedHandle, 0, creationRequestDetails.unmanagedHandle); - if (Platform.IsWindows && (WSManPluginConstants.ExitCodeSuccess == result)) + if (Platform.IsWindows && (result == WSManPluginConstants.ExitCodeSuccess)) { registeredShutdownNotification = 1; @@ -280,7 +277,7 @@ internal void ReportContext() } } - if ((WSManPluginConstants.ExitCodeSuccess != result) || (isRegisterWaitForSingleObjectFailed)) + if ((result != WSManPluginConstants.ExitCodeSuccess) || (isRegisterWaitForSingleObjectFailed)) { string errorMessage; if (isRegisterWaitForSingleObjectFailed) @@ -398,8 +395,8 @@ internal class WSManPluginShellSession : WSManPluginServerSession { #region Private Members - private Dictionary _activeCommandSessions; - private ServerRemoteSession _remoteSession; + private readonly Dictionary _activeCommandSessions; + private readonly ServerRemoteSession _remoteSession; #endregion @@ -471,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. @@ -590,7 +588,7 @@ private void AddToActiveCmdSessions( } IntPtr key = newCmdSession.creationRequestDetails.unmanagedHandle; - Dbg.Assert(IntPtr.Zero != key, "NULL handles should not be provided"); + Dbg.Assert(key != IntPtr.Zero, "NULL handles should not be provided"); if (!_activeCommandSessions.ContainsKey(key)) { @@ -728,7 +726,7 @@ internal class WSManPluginCommandSession : WSManPluginServerSession { #region Private Members - private ServerRemoteSession _remoteSession; + private readonly ServerRemoteSession _remoteSession; #endregion @@ -755,7 +753,7 @@ internal WSManPluginCommandSession( internal bool ProcessArguments( WSManNativeApi.WSManCommandArgSet arguments) { - if (1 != arguments.argsCount) + if (arguments.argsCount != 1) { return false; } @@ -772,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/WSManPluginTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs index a02f6623e0a..9cc10ce435e 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginTransportManager.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + // ---------------------------------------------------------------------- // Contents: Entry points for managed PowerShell plugin worker used to // host powershell in a WSMan service. @@ -22,9 +23,9 @@ internal class WSManPluginServerTransportManager : AbstractServerSessionTranspor // the following variables are used to block thread from sending // data to the client until the client sends a receive request. private bool _isRequestPending; - private object _syncObject; - private ManualResetEvent _waitHandle; - private Dictionary _activeCmdTransportManagers; + private readonly object _syncObject; + private readonly ManualResetEvent _waitHandle; + private readonly Dictionary _activeCmdTransportManagers; private bool _isClosed; // used to keep track of last error..this will be used // for reporting operation complete to WSMan. @@ -180,7 +181,7 @@ internal override void ReportExecutionStatusAsRunning() } } - if ((int)WSManPluginErrorCodes.NoError != result) + if (result != (int)WSManPluginErrorCodes.NoError) { ReportError(result, "WSManPluginReceiveResult"); } @@ -246,7 +247,7 @@ protected override void SendDataToClient( } } - if ((int)WSManPluginErrorCodes.NoError != result) + if (result != (int)WSManPluginErrorCodes.NoError) { ReportError(result, "WSManPluginReceiveResult"); } @@ -303,6 +304,7 @@ internal override void RemoveCommandTransportManager( _activeCmdTransportManagers.Remove(cmdId); } } + #endregion internal bool EnableTransportManagerSendDataToClient( WSManNativeApi.WSManPluginRequest requestDetails, @@ -386,7 +388,7 @@ internal void PerformStop() internal class WSManPluginCommandTransportManager : WSManPluginServerTransportManager { - private WSManPluginServerTransportManager _serverTransportMgr; + private readonly WSManPluginServerTransportManager _serverTransportMgr; private System.Guid _cmdId; // Create Cmd Transport Manager for this sessn transport manager diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs index 1fad7ccb3e8..d4ee779b5a2 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs @@ -38,7 +38,7 @@ internal static class WSManTransportManagerUtils #region Static Data // Fully qualified error Id modifiers based on transport (WinRM) error codes. - private static Dictionary s_transportErrorCodeToFQEID = new Dictionary() + private static readonly Dictionary s_transportErrorCodeToFQEID = new Dictionary() { {WSManNativeApi.ERROR_WSMAN_ACCESS_DENIED, "AccessDenied"}, {WSManNativeApi.ERROR_WSMAN_OUTOF_MEMORY, "ServerOutOfMemory"}, @@ -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) { @@ -318,27 +317,22 @@ internal CompletionEventArgs(CompletionNotification notification) #endregion #region Private Data + // operation handles are owned by WSMan - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSessionHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a session transport manager. private long _sessionContextID; private WSManTransportManagerUtils.tmStartModes _startMode = WSManTransportManagerUtils.tmStartModes.None; - private string _sessionName; + private readonly string _sessionName; // callbacks - private PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; + private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; // instance callback handlers private WSManNativeApi.WSManShellAsync _createSessionCallback; @@ -452,10 +446,11 @@ private void ProcessShellData(string data) // This dictionary maintains active session transport managers to be used from various // callbacks. - private static Dictionary s_sessionTMHandles = + private static readonly Dictionary s_sessionTMHandles = new Dictionary(); private static long s_sessionTMSeed; + // generate unique session id private static long GetNextSessionTMHandleId() { @@ -499,8 +494,8 @@ private static bool TryGetSessionTransportManager(IntPtr operationContext, #region SHIM: Redirection delegates for test purposes - private static Delegate s_sessionSendRedirect = null; - private static Delegate s_protocolVersionRedirect = null; + private static readonly Delegate s_sessionSendRedirect = null; + private static readonly Delegate s_protocolVersionRedirect = null; #endregion @@ -557,9 +552,12 @@ static WSManClientSessionTransportManager() /// /// 1. Create Session failed with a non-zero error code. /// - internal WSManClientSessionTransportManager(Guid runspacePoolInstanceId, + internal WSManClientSessionTransportManager( + Guid runspacePoolInstanceId, WSManConnectionInfo connectionInfo, - PSRemotingCryptoHelper cryptoHelper, string sessionName) : base(runspacePoolInstanceId, cryptoHelper) + PSRemotingCryptoHelper cryptoHelper, + string sessionName) + : base(runspacePoolInstanceId, cryptoHelper) { // Initialize WSMan instance WSManAPIData = new WSManAPIDataCommon(); @@ -922,7 +920,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)); @@ -1030,7 +1030,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."); @@ -1093,7 +1093,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)); @@ -1185,7 +1187,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. @@ -1203,7 +1205,7 @@ internal override void CloseAsync() else if (_startMode == WSManTransportManagerUtils.tmStartModes.Create || _startMode == WSManTransportManagerUtils.tmStartModes.Connect) { - if (IntPtr.Zero == _wsManShellOperationHandle) + if (_wsManShellOperationHandle == IntPtr.Zero) { shouldRaiseCloseCompleted = true; } @@ -1251,7 +1253,7 @@ internal override void CloseAsync() /// Server negotiated protocol version. internal void AdjustForProtocolVariations(Version serverProtocolVersion) { - if (serverProtocolVersion <= RemotingConstants.ProtocolVersionWin7RTM) + if (serverProtocolVersion <= RemotingConstants.ProtocolVersion_2_1) { int maxEnvSize; WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle, @@ -1395,7 +1397,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. @@ -1409,11 +1412,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; @@ -1472,7 +1476,7 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) proxyAuthCredentials = new WSManNativeApi.WSManUserNameAuthenticationCredentials(userName, password, authMechanism); } - WSManNativeApi.WSManProxyInfo proxyInfo = (ProxyAccessType.None == connectionInfo.ProxyAccessType) ? + WSManNativeApi.WSManProxyInfo proxyInfo = (connectionInfo.ProxyAccessType == ProxyAccessType.None) ? null : new WSManNativeApi.WSManProxyInfo(connectionInfo.ProxyAccessType, proxyAuthCredentials); @@ -1488,20 +1492,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) @@ -1546,8 +1539,13 @@ private void Initialize(Uri connectionUri, WSManConnectionInfo connectionInfo) throw new PSRemotingTransportException(PSRemotingErrorId.ConnectFailed, RemotingErrorIdStrings.BasicAuthOverHttpNotSupported); } - // Allow HTTPS on Unix only if SkipCACheck and SkipCNCheck are selected, because OMI client does not support validating server certificates. - if (isSSLSpecified && (!connectionInfo.SkipCACheck || !connectionInfo.SkipCNCheck)) + // The OMI client distributed with PowerShell does not support validating server certificates on Unix. + // Check if third-party psrpclient and MI support the verification. + // If WSManGetSessionOptionAsDword does not return 0 then it's not supported. + bool verificationAvailable = WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle, + WSManNativeApi.WSManSessionOption.WSMAN_OPTION_SKIP_CA_CHECK, out _) == 0; + + if (isSSLSpecified && !verificationAvailable && (!connectionInfo.SkipCACheck || !connectionInfo.SkipCNCheck)) { throw new PSRemotingTransportException(PSRemotingErrorId.ConnectSkipCheckFailed, RemotingErrorIdStrings.UnixOnlyHttpsWithoutSkipCACheckNotSupported); } @@ -1631,7 +1629,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; @@ -1690,7 +1688,7 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) } // For send..clear always - if (IntPtr.Zero != _wsManSendOperationHandle) + if (_wsManSendOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0); _wsManSendOperationHandle = IntPtr.Zero; @@ -1701,7 +1699,7 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) // clearing for receive..Clear only when the end of operation is reached. if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_END_OF_OPERATION) { - if (IntPtr.Zero != _wsManReceiveOperationHandle) + if (_wsManReceiveOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManReceiveOperationHandle, 0); _wsManReceiveOperationHandle = IntPtr.Zero; @@ -1781,7 +1779,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) @@ -1889,7 +1890,7 @@ private static void OnCreateSessionCallback(IntPtr operationContext, } } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -1919,7 +1920,7 @@ private static void OnCreateSessionCallback(IntPtr operationContext, } // check if the session supports disconnect - sessionTM.SupportsDisconnect = ((flags & (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_SUPPORTS_DISCONNECT) != 0) ? true : false; + sessionTM.SupportsDisconnect = (flags & (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_SHELL_SUPPORTS_DISCONNECT) != 0; // openContent is used by redirection ie., while redirecting to // a new machine.. this is not needed anymore as the connection @@ -1990,7 +1991,7 @@ private static void OnCloseSessionCompleted(IntPtr operationContext, sessionTM.RunspacePoolInstanceId.ToString(), "OnCloseSessionCompleted"); - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2049,7 +2050,7 @@ private static void OnRemoteSessionDisconnectCompleted(IntPtr operationContext, sessionTM._disconnectSessionCompleted = null; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2131,7 +2132,7 @@ private static void OnRemoteSessionReconnectCompleted(IntPtr operationContext, sessionTM._reconnectSessionCompleted = null; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2241,7 +2242,7 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2280,7 +2281,7 @@ private static void OnRemoteSessionConnectCallback(IntPtr operationContext, } // process returned Xml - Dbg.Assert(data != null, "WSManConnectShell callback returned null data"); + Dbg.Assert(data != IntPtr.Zero, "WSManConnectShell callback returned null data"); WSManNativeApi.WSManConnectDataResult connectData = WSManNativeApi.WSManConnectDataResult.UnMarshal(data); if (connectData.data != null) { @@ -2348,7 +2349,7 @@ private static void OnRemoteSessionSendCompleted(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2418,7 +2419,7 @@ private static void OnRemoteSessionDataReceived(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); @@ -2534,7 +2535,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); @@ -2571,7 +2572,7 @@ private void CloseSessionAndClearResources() ThreadPool.QueueUserWorkItem(new WaitCallback( // wsManSessionHandle is passed as parameter to allow the thread to be independent // of the rest of the parent object. - delegate (object state) + (object state) => { IntPtr sessionHandle = (IntPtr)state; if (sessionHandle != IntPtr.Zero) @@ -2614,18 +2615,17 @@ private void CloseSessionAndClearResources() private void DisposeWSManAPIDataAsync() { WSManAPIDataCommon tempWSManApiData = WSManAPIData; - if (tempWSManApiData == null) { return; } + if (tempWSManApiData == null) + { + return; + } WSManAPIData = null; // Dispose and de-initialize the WSManAPIData instance object on separate worker thread to ensure // it is not run on a WinRM thread (which will fail). // Note that WSManAPIData.Dispose() method is thread safe. - System.Threading.ThreadPool.QueueUserWorkItem( - (state) => - { - tempWSManApiData.Dispose(); - }); + ThreadPool.QueueUserWorkItem((_) => tempWSManApiData.Dispose()); } #endregion @@ -2638,16 +2638,15 @@ private void DisposeWSManAPIDataAsync() /// internal class WSManAPIDataCommon : IDisposable { - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _handle; // if any private WSManNativeApi.WSManStreamIDSet_ManToUn _inputStreamSet; private WSManNativeApi.WSManStreamIDSet_ManToUn _outputStreamSet; // Dispose private bool _isDisposed; - private object _syncObject = new object(); + private readonly object _syncObject = new object(); #if !UNIX - private WindowsIdentity _identityToImpersonate; + private readonly WindowsIdentity _identityToImpersonate; #endif /// @@ -2721,7 +2720,10 @@ public void Dispose() { lock (_syncObject) { - if (_isDisposed) { return; } + if (_isDisposed) + { + return; + } _isDisposed = true; } @@ -2729,7 +2731,7 @@ public void Dispose() _inputStreamSet.Dispose(); _outputStreamSet.Dispose(); - if (IntPtr.Zero != _handle) + if (_handle != IntPtr.Zero) { int result = 0; @@ -2782,23 +2784,16 @@ internal sealed class WSManClientCommandTransportManager : BaseClientCommandTran #region Private Data // operation handles - private IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] + private readonly IntPtr _wsManShellOperationHandle; private IntPtr _wsManCmdOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _cmdSignalOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a command transport manager. private long _cmdContextId; - private PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; + private readonly PrioritySendDataCollection.OnDataAvailableCallback _onDataAvailableToSendCallback; // should be integrated with receiveDataInitiated private bool _shouldStartReceivingData; @@ -2825,10 +2820,10 @@ internal sealed class WSManClientCommandTransportManager : BaseClientCommandTran // will be sent during subsequent SendOneItem() private SendDataChunk _chunkToSend; - private string _cmdLine; + private readonly string _cmdLine; private readonly WSManClientSessionTransportManager _sessnTm; - private class SendDataChunk + private sealed class SendDataChunk { public SendDataChunk(byte[] data, DataPriorityType type) { @@ -2910,14 +2905,15 @@ static WSManClientCommandTransportManager() /// Session transport manager creating this command transport manager instance. /// Used by Command TM to apply session specific properties /// - internal WSManClientCommandTransportManager(WSManConnectionInfo connectionInfo, + internal WSManClientCommandTransportManager( + WSManConnectionInfo connectionInfo, IntPtr wsManShellOperationHandle, ClientRemotePowerShell shell, bool noInput, - WSManClientSessionTransportManager sessnTM) : - base(shell, sessnTM.CryptoHelper, sessnTM) + WSManClientSessionTransportManager sessnTM) + : base(shell, sessnTM.CryptoHelper, sessnTM) { - Dbg.Assert(IntPtr.Zero != wsManShellOperationHandle, "Shell operation handle cannot be IntPtr.Zero."); + Dbg.Assert(wsManShellOperationHandle != IntPtr.Zero, "Shell operation handle cannot be IntPtr.Zero."); Dbg.Assert(connectionInfo != null, "connectionInfo cannot be null"); _wsManShellOperationHandle = wsManShellOperationHandle; @@ -2940,7 +2936,7 @@ internal WSManClientCommandTransportManager(WSManConnectionInfo connectionInfo, #region SHIM: Redirection delegate for command code send. - private static Delegate s_commandCodeSendRedirect = null; + private static readonly Delegate s_commandCodeSendRedirect = null; #endregion @@ -3007,11 +3003,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) @@ -3140,7 +3137,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); @@ -3159,7 +3156,7 @@ internal override void CloseAsync() // There is no valid cmd operation handle..so just // raise close completed. - if (IntPtr.Zero == _wsManCmdOperationHandle) + if (_wsManCmdOperationHandle == IntPtr.Zero) { shouldRaiseCloseCompleted = true; } @@ -3207,7 +3204,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; @@ -3285,7 +3282,7 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) } // For send..clear always - if (IntPtr.Zero != _wsManSendOperationHandle) + if (_wsManSendOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManSendOperationHandle, 0); _wsManSendOperationHandle = IntPtr.Zero; @@ -3296,7 +3293,7 @@ internal void ClearReceiveOrSendResources(int flags, bool shouldClearSend) // clearing for receive..Clear only when the end of operation is reached. if (flags == (int)WSManNativeApi.WSManCallbackFlags.WSMAN_FLAG_CALLBACK_END_OF_OPERATION) { - if (IntPtr.Zero != _wsManReceiveOperationHandle) + if (_wsManReceiveOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(_wsManReceiveOperationHandle, 0); _wsManReceiveOperationHandle = IntPtr.Zero; @@ -3385,7 +3382,7 @@ private static void OnCreateCmdCompleted(IntPtr operationContext, // Remove this once WSMan fixes its code. cmdTM._wsManCmdOperationHandle = commandOperationHandle; - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) @@ -3483,7 +3480,7 @@ private static void OnConnectCmdCompleted(IntPtr operationContext, cmdTM._wsManCmdOperationHandle = commandOperationHandle; - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) @@ -3654,7 +3651,7 @@ private static void OnRemoteCmdSendCompleted(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); // Ignore Command aborted error. Command aborted is raised by WSMan to @@ -3731,7 +3728,7 @@ private static void OnRemoteCmdDataReceived(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) @@ -3811,7 +3808,7 @@ private static void OnReconnectCmdCompleted(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) @@ -3884,7 +3881,7 @@ private static void OnRemoteCmdSignalCompleted(IntPtr operationContext, } // release the resources related to signal - if (IntPtr.Zero != cmdTM._cmdSignalOperationHandle) + if (cmdTM._cmdSignalOperationHandle != IntPtr.Zero) { WSManNativeApi.WSManCloseOperation(cmdTM._cmdSignalOperationHandle, 0); cmdTM._cmdSignalOperationHandle = IntPtr.Zero; @@ -3903,7 +3900,7 @@ private static void OnRemoteCmdSignalCompleted(IntPtr operationContext, return; } - if (IntPtr.Zero != error) + if (error != IntPtr.Zero) { WSManNativeApi.WSManError errorStruct = WSManNativeApi.WSManError.UnMarshal(error); if (errorStruct.errorCode != 0) @@ -3986,7 +3983,7 @@ private void OnDataAvailableCallback(byte[] data, DataPriorityType priorityType) #region SHIM: Redirection delegate for command data send. - private static Delegate s_commandSendRedirect = null; + private static readonly Delegate s_commandSendRedirect = null; #endregion @@ -4080,7 +4077,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); @@ -4116,7 +4113,7 @@ internal override void Dispose(bool isDisposing) // This dictionary maintains active command transport managers to be used from various // callbacks. - private static Dictionary s_cmdTMHandles = + private static readonly Dictionary s_cmdTMHandles = new Dictionary(); private static long s_cmdTMSeed; diff --git a/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs b/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs index 63185ebc13f..e24f8961b9a 100644 --- a/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs +++ b/src/System.Management.Automation/engine/remoting/host/RemoteHostMethodInfo.cs @@ -144,35 +144,35 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHost), "get_Name", typeof(string), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetVersion: return new RemoteHostMethodInfo( typeof(PSHost), "get_Version", typeof(Version), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetInstanceId: return new RemoteHostMethodInfo( typeof(PSHost), "get_InstanceId", typeof(Guid), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetCurrentCulture: return new RemoteHostMethodInfo( typeof(PSHost), "get_CurrentCulture", typeof(System.Globalization.CultureInfo), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetCurrentUICulture: return new RemoteHostMethodInfo( typeof(PSHost), "get_CurrentUICulture", typeof(System.Globalization.CultureInfo), - new Type[] { }); + Array.Empty()); // Host methods. @@ -188,28 +188,28 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHost), "EnterNestedPrompt", typeof(void), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.ExitNestedPrompt: return new RemoteHostMethodInfo( typeof(PSHost), "ExitNestedPrompt", typeof(void), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.NotifyBeginApplication: return new RemoteHostMethodInfo( typeof(PSHost), "NotifyBeginApplication", typeof(void), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.NotifyEndApplication: return new RemoteHostMethodInfo( typeof(PSHost), "NotifyEndApplication", typeof(void), - new Type[] { }); + Array.Empty()); // Host UI methods. @@ -218,14 +218,14 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostUserInterface), "ReadLine", typeof(string), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.ReadLineAsSecureString: return new RemoteHostMethodInfo( typeof(PSHostUserInterface), "ReadLineAsSecureString", typeof(System.Security.SecureString), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.Write1: return new RemoteHostMethodInfo( @@ -246,7 +246,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostUserInterface), "WriteLine", typeof(void), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.WriteLine2: return new RemoteHostMethodInfo( @@ -339,7 +339,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_ForegroundColor", typeof(ConsoleColor), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetForegroundColor: return new RemoteHostMethodInfo( @@ -353,7 +353,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_BackgroundColor", typeof(ConsoleColor), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetBackgroundColor: return new RemoteHostMethodInfo( @@ -367,7 +367,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_CursorPosition", typeof(Coordinates), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetCursorPosition: return new RemoteHostMethodInfo( @@ -381,7 +381,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_WindowPosition", typeof(Coordinates), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetWindowPosition: return new RemoteHostMethodInfo( @@ -395,7 +395,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_CursorSize", typeof(int), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetCursorSize: return new RemoteHostMethodInfo( @@ -409,7 +409,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_BufferSize", typeof(Size), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetBufferSize: return new RemoteHostMethodInfo( @@ -423,7 +423,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_WindowSize", typeof(Size), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetWindowSize: return new RemoteHostMethodInfo( @@ -437,7 +437,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_WindowTitle", typeof(string), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetWindowTitle: return new RemoteHostMethodInfo( @@ -453,21 +453,21 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "get_MaxWindowSize", typeof(Size), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetMaxPhysicalWindowSize: return new RemoteHostMethodInfo( typeof(PSHostRawUserInterface), "get_MaxPhysicalWindowSize", typeof(Size), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetKeyAvailable: return new RemoteHostMethodInfo( typeof(PSHostRawUserInterface), "get_KeyAvailable", typeof(bool), - new Type[] { }); + Array.Empty()); // Host raw UI methods. @@ -483,7 +483,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(PSHostRawUserInterface), "FlushInputBuffer", typeof(void), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.SetBufferContents1: return new RemoteHostMethodInfo( @@ -527,7 +527,7 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(IHostSupportsInteractiveSession), "PopRunspace", typeof(void), - new Type[] { }); + Array.Empty()); // IHostSupportsInteractiveSession properties. @@ -536,14 +536,14 @@ internal static RemoteHostMethodInfo LookUp(RemoteHostMethodId methodId) typeof(IHostSupportsInteractiveSession), "get_IsRunspacePushed", typeof(bool), - new Type[] { }); + Array.Empty()); case RemoteHostMethodId.GetRunspace: return new RemoteHostMethodInfo( typeof(IHostSupportsInteractiveSession), "get_Runspace", typeof(System.Management.Automation.Runspaces.Runspace), - new Type[] { }); + Array.Empty()); default: Dbg.Assert(false, "All RemoteHostMethodId's should be handled. This code should not be reached."); diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs index a89f1a9d285..6c794e21b24 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); } @@ -125,7 +116,7 @@ protected void OnDataPacketReceived(byte[] rawData, string stream, Guid psGuid) streamTemp = System.Management.Automation.Remoting.Client.WSManNativeApi.WSMAN_STREAM_ID_PROMPTRESPONSE; } - if (Guid.Empty == psGuid) + if (psGuid == Guid.Empty) { lock (_syncObject) { @@ -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,35 +309,51 @@ 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 { - do + while (true) { 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)) @@ -381,7 +383,6 @@ protected void Start(string initialCommand, PSRemotingCryptoHelperServer cryptoH ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessingThreadStart), data); #endif } - while (true); } catch (Exception e) { @@ -418,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 @@ -454,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 @@ -467,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); } @@ -491,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) { @@ -566,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 @@ -611,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. @@ -638,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) { } @@ -675,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); } @@ -715,6 +635,16 @@ private HyperVSocketMediator() originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter); } + private HyperVSocketMediator(string token, + DateTimeOffset tokenCreationTime) + : base(false) + { + _hypervSocketServer = new RemoteSessionHyperVSocketServer(false, token: token, tokenCreationTime: tokenCreationTime); + + originalStdIn = _hypervSocketServer.TextReader; + originalStdOut = new OutOfProcessTextWriter(_hypervSocketServer.TextWriter); + originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter); + } #endregion #region Static Methods @@ -728,14 +658,32 @@ 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); } + internal static void Run( + string initialCommand, + string configurationName, + string token, + DateTimeOffset tokenCreationTime) + { + lock (SyncObject) + { + s_instance = new HyperVSocketMediator(token, tokenCreationTime); + } + + s_instance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: null, + configurationName: configurationName, + configurationFile: null); + } #endregion } @@ -767,7 +715,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/ServerMethodExecutor.cs b/src/System.Management.Automation/engine/remoting/server/ServerMethodExecutor.cs index 0c0fb0a0c42..d98239c3c33 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerMethodExecutor.cs @@ -21,27 +21,27 @@ internal class ServerMethodExecutor /// /// Client runspace pool id. /// - private Guid _clientRunspacePoolId; + private readonly Guid _clientRunspacePoolId; /// /// Client power shell id. /// - private Guid _clientPowerShellId; + private readonly Guid _clientPowerShellId; /// /// Server dispatch table. /// - private ServerDispatchTable _serverDispatchTable; + private readonly ServerDispatchTable _serverDispatchTable; /// /// Remote host call data type. /// - private RemotingDataType _remoteHostCallDataType; + private readonly RemotingDataType _remoteHostCallDataType; /// /// Transport manager. /// - private AbstractServerTransportManager _transportManager; + private readonly AbstractServerTransportManager _transportManager; /// /// Constructor for ServerMethodExecutor. @@ -92,7 +92,7 @@ internal void ExecuteVoidMethod(RemoteHostMethodId methodId, object[] parameters Dbg.Assert(parameters != null, "Expected parameters != null"); // Use void call ID so that the call is known to not have a return value. - long callId = ServerDispatchTable.VoidCallId; + const long callId = ServerDispatchTable.VoidCallId; RemoteHostCall remoteHostCall = new RemoteHostCall(callId, methodId, parameters); // Dispatch the call but don't wait for response since the return value is void. diff --git a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs index e52cffd5ce0..ddeb81aae17 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerPowerShellDriver.cs @@ -23,31 +23,31 @@ internal class ServerPowerShellDriver private bool _extraPowerShellAlreadyScheduled; // extra PowerShell at the server to be run after localPowerShell - private PowerShell _extraPowerShell; + private readonly PowerShell _extraPowerShell; // output buffer for the local PowerShell that is associated with this powershell driver // associated with this powershell data structure handler object to handle all communications with the client - private PSDataCollection _localPowerShellOutput; + private readonly PSDataCollection _localPowerShellOutput; // if the remaining data has been sent to the client before sending state information - private bool[] _datasent = new bool[2]; + private readonly bool[] _datasent = new bool[2]; // sync object for synchronizing sending data to client - private object _syncObject = new object(); + private readonly object _syncObject = new object(); // there is no input when this driver was created - private bool _noInput; - private bool _addToHistory; + private readonly bool _noInput; + private readonly bool _addToHistory; // the server remote host instance // associated with this powershell - private ServerRemoteHost _remoteHost; + private readonly ServerRemoteHost _remoteHost; // apartment state for this powershell - private ApartmentState apartmentState; + private readonly ApartmentState apartmentState; // Handles nested invocation of PS drivers. - private IRSPDriverInvoke _psDriverInvoker; + private readonly IRSPDriverInvoke _psDriverInvoker; #endregion Private Members @@ -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 921f9f456f1..71dda0d0b7a 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -20,22 +20,22 @@ internal class ServerRemoteHost : PSHost, IHostSupportsInteractiveSession /// /// Remote host user interface. /// - private ServerRemoteHostUserInterface _remoteHostUserInterface; + private readonly ServerRemoteHostUserInterface _remoteHostUserInterface; /// /// Server method executor. /// - private ServerMethodExecutor _serverMethodExecutor; + private readonly ServerMethodExecutor _serverMethodExecutor; /// /// Client runspace pool id. /// - private Guid _clientRunspacePoolId; + private readonly Guid _clientRunspacePoolId; /// /// Client power shell id. /// - private Guid _clientPowerShellId; + private readonly Guid _clientPowerShellId; /// /// Transport manager. @@ -45,7 +45,7 @@ internal class ServerRemoteHost : PSHost, IHostSupportsInteractiveSession /// /// ServerDriverRemoteHost. /// - private ServerDriverRemoteHost _serverDriverRemoteHost; + private readonly ServerDriverRemoteHost _serverDriverRemoteHost; #endregion @@ -327,16 +327,15 @@ public override void PushRunspace(Runspace runspace) throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostAlreadyPushed); } - RemoteRunspace remoteRunspace = runspace as RemoteRunspace; - if (remoteRunspace == null) + if (runspace is not RemoteRunspace remoteRunspace) { throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNotRemoteRunspace); } // PSEdit support. Existence of RemoteSessionOpenFileEvent event indicates host supports PSEdit _hostSupportsPSEdit = false; - PSEventManager localEventManager = (Runspace != null) ? Runspace.Events : null; - _hostSupportsPSEdit = (localEventManager != null) ? localEventManager.GetEventSubscribers(HostUtilities.RemoteSessionOpenFileEvent).GetEnumerator().MoveNext() : false; + PSEventManager localEventManager = Runspace?.Events; + _hostSupportsPSEdit = localEventManager != null && localEventManager.GetEventSubscribers(HostUtilities.RemoteSessionOpenFileEvent).GetEnumerator().MoveNext(); if (_hostSupportsPSEdit) { AddPSEditForRunspace(remoteRunspace); @@ -353,10 +352,7 @@ public override void PopRunspace() { if (_pushedRunspace != null) { - if (_debugger != null) - { - _debugger.PopDebugger(); - } + _debugger?.PopDebugger(); if (_hostSupportsPSEdit) { @@ -411,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; @@ -431,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)) @@ -457,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 1a709b11246..34c10e0a9a0 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostRawUserInterface.cs @@ -17,12 +17,12 @@ internal class ServerRemoteHostRawUserInterface : PSHostRawUserInterface /// /// Remote host user interface. /// - private ServerRemoteHostUserInterface _remoteHostUserInterface; + private readonly ServerRemoteHostUserInterface _remoteHostUserInterface; /// /// Server method executor. /// - private ServerMethodExecutor _serverMethodExecutor; + private readonly ServerMethodExecutor _serverMethodExecutor; /// /// Host default data. @@ -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 6b69bb07b84..76265089677 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHostUserInterface.cs @@ -20,7 +20,7 @@ internal class ServerRemoteHostUserInterface : PSHostUserInterface, IHostUISuppo /// /// Server method executor. /// - private ServerMethodExecutor _serverMethodExecutor; + private readonly ServerMethodExecutor _serverMethodExecutor; /// /// Constructor for ServerRemoteHostUserInterface. @@ -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,20 +201,10 @@ public override void WriteVerboseLine(string message) /// public override void WriteWarningLine(string message) { + message = GetOutputString(message, supportsVirtualTerminal: true); _serverMethodExecutor.ExecuteVoidMethod(RemoteHostMethodId.WriteWarningLine, new object[] { message }); } - /// - /// Read line as string masked. - /// - /// - /// Not implemented. It throws an exception. - /// - public override string ReadLineMaskedAsString() - { - throw new PSNotImplementedException(); - } - /// /// Read line as secure string. /// diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemotingProtocol2.cs index 72e7db24ae5..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) { @@ -409,17 +406,18 @@ private void HandleRemoveAssociation(object sender, EventArgs e) #region Private Members - private Guid _clientRunspacePoolId; + private readonly Guid _clientRunspacePoolId; // transport manager using which this // runspace pool driver handles all client // communication - private AbstractServerSessionTransportManager _transportManager; + private readonly AbstractServerSessionTransportManager _transportManager; - private Dictionary _associatedShells + private readonly Dictionary _associatedShells = new Dictionary(); + // powershell data structure handlers associated with this // runspace pool data structure handler - private object _associationSyncObject = new object(); + private readonly object _associationSyncObject = new object(); // object to synchronize operations to above #endregion Private Members @@ -435,10 +433,10 @@ internal class ServerPowerShellDataStructureHandler // transport manager using which this // powershell driver handles all client // communication - private AbstractServerTransportManager _transportManager; - private Guid _clientRunspacePoolId; - private Guid _clientPowerShellId; - private RemoteStreamOptions _streamSerializationOptions; + private readonly AbstractServerTransportManager _transportManager; + private readonly Guid _clientRunspacePoolId; + private readonly Guid _clientPowerShellId; + private readonly RemoteStreamOptions _streamSerializationOptions; private Runspace _rsUsedToInvokePowerShell; #endregion Private Members @@ -787,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 10814c3ea2b..a5e0da4968e 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -28,12 +28,16 @@ namespace System.Management.Automation /// Interface exposing driver single thread invoke enter/exit /// nested pipeline. /// +#nullable enable internal interface IRSPDriverInvoke { void EnterNestedPipeline(); + void ExitNestedPipeline(); + bool HandleStopSignal(); } +#nullable restore /// /// This class wraps a RunspacePoolInternal object. It is used to function @@ -49,7 +53,7 @@ internal class ServerRunspacePoolDriver : IRSPDriverInvoke private readonly string _initialLocation; // Script to run after a RunspacePool/Runspace is created in this session. - private ConfigurationDataFromXML _configData; + private readonly ConfigurationDataFromXML _configData; // application private data to send back to the client in when we get into "opened" state private PSPrimitiveDictionary _applicationPrivateData; @@ -61,35 +65,35 @@ internal class ServerRunspacePoolDriver : IRSPDriverInvoke // with the client // powershell's associated with this runspace pool - private Dictionary _associatedShells + private readonly Dictionary _associatedShells = new Dictionary(); // remote host associated with this runspacepool - private ServerDriverRemoteHost _remoteHost; + private readonly ServerDriverRemoteHost _remoteHost; private bool _isClosed; // server capability reported to the client during negotiation (not the actual capability) - private RemoteSessionCapability _serverCapability; + private readonly RemoteSessionCapability _serverCapability; private Runspace _rsToUseForSteppablePipeline; // steppable pipeline event subscribers exist per-session - private ServerSteppablePipelineSubscriber _eventSubscriber = new ServerSteppablePipelineSubscriber(); + private readonly ServerSteppablePipelineSubscriber _eventSubscriber = new ServerSteppablePipelineSubscriber(); private PSDataCollection _inputCollection; // PowerShell driver input collection // Object to invoke nested PowerShell drivers on single pipeline worker thread. - private PowerShellDriverInvoker _driverNestedInvoker; + private readonly PowerShellDriverInvoker _driverNestedInvoker; // Remote wrapper for script debugger. private ServerRemoteDebugger _serverRemoteDebugger; // Version of PowerShell client. - private Version _clientPSVersion; + private readonly Version _clientPSVersion; // Optional endpoint configuration name. // Used in OutOfProc scenarios that do not support PSSession endpoint configuration. // Results in a configured remote runspace pushed onto driver host. - private string _configurationName; + private readonly string _configurationName; /// /// Event that get raised when the RunspacePool is closed. @@ -164,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; @@ -180,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) { @@ -267,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) { @@ -346,10 +347,7 @@ internal void Close() { Runspace runspaceToDispose = _remoteHost.PushedRunspace; _remoteHost.PopRunspace(); - if (runspaceToDispose != null) - { - runspaceToDispose.Dispose(); - } + runspaceToDispose?.Dispose(); } DisposeRemoteDebugger(); @@ -474,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); @@ -482,13 +480,7 @@ private void SetupRemoteDebugger(Runspace runspace) } } - private void DisposeRemoteDebugger() - { - if (_serverRemoteDebugger != null) - { - _serverRemoteDebugger.Dispose(); - } - } + private void DisposeRemoteDebugger() => _serverRemoteDebugger?.Dispose(); /// /// Invokes a script. @@ -557,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 { @@ -724,7 +716,7 @@ private void HandleCreateAndInvokePowerShell(object _, RemoteDataEventArgs= RemotingConstants.ProtocolVersionWin8RTM) + if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_2) { isNested = RemotingDecoder.GetIsNested(data.Data); } @@ -808,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) { @@ -1115,7 +1101,7 @@ private void HandleGetAvailableRunspacesReceived(object sender, RemoteDataEventA } /// - /// Forces a state reset on a single runspace runspace pool. + /// Forces a state reset on a single runspace pool. /// /// /// @@ -1246,9 +1232,9 @@ private enum PreProcessCommandResult /// The PreProcessCommandResult used for managing breakpoints. /// BreakpointManagement, - }; + } - private class DebuggerCommandArgument + private sealed class DebuggerCommandArgument { public DebugModes? Mode { get; set; } @@ -1484,9 +1470,7 @@ private static PreProcessCommandResult PreProcessDebuggerCommand( Breakpoint breakpoint = serverRemoteDebugger.GetBreakpoint(breakpointId, runspaceId); preProcessOutput.Add( - breakpoint == null - ? false - : serverRemoteDebugger.RemoveBreakpoint(breakpoint, runspaceId)); + breakpoint != null && serverRemoteDebugger.RemoveBreakpoint(breakpoint, runspaceId)); result = PreProcessCommandResult.BreakpointManagement; } @@ -1587,7 +1571,7 @@ private sealed class PowerShellDriverInvoker { #region Private Members - private ConcurrentStack _invokePumpStack; + private readonly ConcurrentStack _invokePumpStack; #endregion @@ -1626,7 +1610,7 @@ public bool IsAvailable pump = null; } - return (pump != null) ? !(pump.IsBusy) : false; + return (pump != null) && !(pump.IsBusy); } } @@ -1690,9 +1674,9 @@ public void PopInvoker() /// private sealed class InvokePump { - private Queue _driverInvokeQueue; - private ManualResetEvent _processDrivers; - private object _syncObject; + private readonly Queue _driverInvokeQueue; + private readonly ManualResetEvent _processDrivers; + private readonly object _syncObject; private bool _stopPump; private bool _isDisposed; @@ -1802,9 +1786,9 @@ internal sealed class ServerRemoteDebugger : Debugger, IDisposable { #region Private Members - private IRSPDriverInvoke _driverInvoker; - private Runspace _runspace; - private ObjectRef _wrappedDebugger; + private readonly IRSPDriverInvoke _driverInvoker; + private readonly Runspace _runspace; + private readonly ObjectRef _wrappedDebugger; private bool _inDebugMode; private DebuggerStopEventArgs _debuggerStopEventArgs; @@ -2002,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 @@ -2029,10 +2010,7 @@ public override void StopProcessCommand() } ThreadCommandProcessing threadCommandProcessing = _threadCommandProcessing; - if (threadCommandProcessing != null) - { - threadCommandProcessing.Stop(); - } + threadCommandProcessing?.Stop(); } /// @@ -2064,7 +2042,7 @@ public override bool IsActive public override void SetDebuggerStepMode(bool enabled) { // Enable both the wrapper and wrapped debuggers for debugging before setting step mode. - DebugModes mode = DebugModes.LocalScript | DebugModes.RemoteScript; + const DebugModes mode = DebugModes.LocalScript | DebugModes.RemoteScript; base.SetDebugMode(mode); _wrappedDebugger.Value.SetDebugMode(mode); @@ -2230,15 +2208,8 @@ public void Dispose() ExitDebugMode(DebuggerResumeAction.Stop); } - if (_nestedDebugStopCompleteEvent != null) - { - _nestedDebugStopCompleteEvent.Dispose(); - } - - if (_processCommandCompleteEvent != null) - { - _processCommandCompleteEvent.Dispose(); - } + _nestedDebugStopCompleteEvent?.Dispose(); + _processCommandCompleteEvent?.Dispose(); } #endregion @@ -2248,10 +2219,10 @@ public void Dispose() private sealed class ThreadCommandProcessing { // Members - private ManualResetEventSlim _commandCompleteEvent; - private Debugger _wrappedDebugger; - private PSCommand _command; - private PSDataCollection _output; + private readonly ManualResetEventSlim _commandCompleteEvent; + private readonly Debugger _wrappedDebugger; + private readonly PSCommand _command; + private readonly PSDataCollection _output; private DebuggerCommandResults _results; private Exception _exception; #if !UNIX @@ -2310,10 +2281,7 @@ public DebuggerCommandResults Invoke(ManualResetEventSlim startInvokeEvent) public void Stop() { Debugger debugger = _wrappedDebugger; - if (debugger != null) - { - debugger.StopProcessCommand(); - } + debugger?.StopProcessCommand(); } internal void DoInvoke() @@ -2418,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) { @@ -2474,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) { @@ -2525,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); @@ -2710,7 +2681,7 @@ internal void StartPowerShellCommand( powershell.InvocationStateChanged += HandlePowerShellInvocationStateChanged; powershell.SetIsNested(false); - string script = @" + const string script = @" param ($Debugger, $Commands, $output) trap { throw $_ } @@ -2797,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 240395bb567..3a9388625e6 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineDriver.cs @@ -14,9 +14,9 @@ namespace System.Management.Automation /// /// Execution context used for stepping. /// - internal class ExecutionContextForStepping : IDisposable + internal sealed class ExecutionContextForStepping : IDisposable { - private ExecutionContext _executionContext; + private readonly ExecutionContext _executionContext; private PSInformationalBuffers _originalInformationalBuffers; private PSHost _originalHost; @@ -72,13 +72,13 @@ internal class ServerSteppablePipelineDriver // information // data to client // was created - private bool _addToHistory; + private readonly bool _addToHistory; // associated with this powershell - private ApartmentState apartmentState; // apartment state for this powershell + private readonly ApartmentState apartmentState; // apartment state for this powershell // pipeline that runs the actual command. - private ServerSteppablePipelineSubscriber _eventSubscriber; - private PSDataCollection _powershellInput; // input collection of the PowerShell pipeline + private readonly ServerSteppablePipelineSubscriber _eventSubscriber; + private readonly PSDataCollection _powershellInput; // input collection of the PowerShell pipeline #endregion @@ -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 850993cf162..cce8b0bbcd3 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerSteppablePipelineSubscriber.cs @@ -29,7 +29,7 @@ internal class ServerSteppablePipelineSubscriber { #region Private data - private object _syncObject = new object(); + private readonly object _syncObject = new object(); private bool _initialized = false; private PSLocalEventManager _eventManager; private PSEventSubscriber _startSubscriber; @@ -62,6 +62,7 @@ internal void SubscribeEvents(ServerSteppablePipelineDriver driver) #region Events and Handlers public event EventHandler StartSteppablePipeline; + public event EventHandler RunProcessRecord; /// @@ -161,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; } } @@ -273,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); } } @@ -289,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/WSManChannelEvents.cs b/src/System.Management.Automation/engine/remoting/server/WSManChannelEvents.cs index 3b1174f0fff..9fbcb9e0cf9 100644 --- a/src/System.Management.Automation/engine/remoting/server/WSManChannelEvents.cs +++ b/src/System.Management.Automation/engine/remoting/server/WSManChannelEvents.cs @@ -30,11 +30,7 @@ public static class WSManServerChannelEvents /// internal static void RaiseShuttingDownEvent() { - EventHandler handler = ShuttingDown; - if (handler != null) - { - handler(null, EventArgs.Empty); - } + ShuttingDown?.Invoke(null, EventArgs.Empty); } /// @@ -42,11 +38,7 @@ internal static void RaiseShuttingDownEvent() /// internal static void RaiseActiveSessionsChangedEvent(ActiveSessionsChangedEventArgs eventArgs) { - EventHandler handler = ActiveSessionsChanged; - if (handler != null) - { - handler(null, eventArgs); - } + ActiveSessionsChanged?.Invoke(null, eventArgs); } #endregion internal members diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index 16978ff04e3..b1e05909d2c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -69,12 +69,12 @@ internal ServerRemoteSessionContext() /// internal class ServerRemoteSession : RemoteSession { - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); - private PSSenderInfo _senderInfo; - private string _configProviderId; - private string _initParameters; + private readonly PSSenderInfo _senderInfo; + private readonly string _configProviderId; + private readonly string _initParameters; private string _initScriptForOutOfProcRS; private PSSessionConfiguration _sessionConfigProvider; @@ -83,12 +83,16 @@ internal class ServerRemoteSession : RemoteSession private int? _maxRecvdDataSizeCommand; private ServerRunspacePoolDriver _runspacePoolDriver; - private PSRemotingCryptoHelperServer _cryptoHelper; + private readonly PSRemotingCryptoHelperServer _cryptoHelper; // Specifies an optional endpoint configuration for out-of-proc session use. // 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), @@ -206,7 +214,7 @@ internal static ServerRemoteSession CreateServerRemoteSession( throw PSTraceSource.NewInvalidOperationException("RemotingErrorIdStrings.NonExistentInitialSessionStateProvider", configurationProviderId); } - string shellPrefix = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix; + const string shellPrefix = System.Management.Automation.Remoting.Client.WSManNativeApi.ResourceURIPrefix; int index = configurationProviderId.IndexOf(shellPrefix, StringComparison.OrdinalIgnoreCase); senderInfo.ConfigurationName = (index == 0) ? configurationProviderId.Substring(shellPrefix.Length) : string.Empty; ServerRemoteSession result = new ServerRemoteSession( @@ -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 @@ -285,10 +268,10 @@ internal override RemotingDestination MySelf /// This parameter contains the remote data received from client. /// /// - /// If the parameter is null. + /// If the parameter is null. /// /// - /// If the parameter does not contain remote data. + /// If the parameter does not contain remote data. /// /// /// If the destination of the data is not for server. @@ -696,7 +679,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) // as this is executed only when connecting from a new client that does not have any previous fragments context. // no problem even if fragment Ids in this response and the sessiontransport stream clash (interfere) and its guaranteed // that the fragments in connect response are always complete (enclose a complete object). - SerializedDataStream stream = new SerializedDataStream(4 * 1024);//Each message with fragment headers cannot cross 4k + SerializedDataStream stream = new SerializedDataStream(4 * 1024); //Each message with fragment headers cannot cross 4k stream.Enter(); capability.Serialize(stream, fragmentor); stream.Exit(); @@ -712,7 +695,7 @@ internal void ExecuteConnect(byte[] connectData, out byte[] connectResponseData) // enqueue a connect event in state machine to let session do any other post-connect operation // Do this outside of the synchronous connect operation, as otherwise connect can easily get deadlocked ThreadPool.QueueUserWorkItem(new WaitCallback( - delegate (object state) + (object state) => { RemoteSessionStateMachineEventArgs startEventArg = new RemoteSessionStateMachineEventArgs(RemoteSessionEvent.ConnectSession); SessionDataStructureHandler.StateMachine.RaiseEvent(startEventArg); @@ -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. @@ -1016,35 +973,21 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil if (onConnect) { - bool connectSupported = false; - - // Win10 server can support reconstruct/reconnect for all 2.x protocol versions - // that support reconstruct/reconnect, Protocol 2.2+ - // Major protocol version differences (2.x -> 3.x) are not supported. - // A reconstruct can only be initiated by a client that understands disconnect (2.2+), - // so we only need to check major versions from client and this server for compatibility. - if (clientProtocolVersion.Major == RemotingConstants.ProtocolVersion.Major) + // PS v7.6 server can support reconstruct/reconnect for all 2.x protocol versions that support reconstruct/reconnect (v2.2+). + // Major protocol version differences (2.x -> 3.x) are not supported. A reconstruct can only be initiated by a client that understands disconnect (v2.2+). + if (clientProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { - if (clientProtocolVersion.Minor == RemotingConstants.ProtocolVersionWin8RTM.Minor) - { - // Report that server is Win8 version to the client - // Protocol: 2.2 - connectSupported = true; - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - else if (clientProtocolVersion.Minor > RemotingConstants.ProtocolVersionWin8RTM.Minor) - { - // All other minor versions are supported and the server returns its full capability - // Protocol: 2.3, 2.4, 2.5 ... - connectSupported = true; - } + // Report the server as the same version to the client. + // Client protocol: v2.2, v2.3 + serverProtocolVersion = clientProtocolVersion; + Context.ServerCapability.ProtocolVersion = serverProtocolVersion; } - - if (!connectSupported) + else if (!(clientProtocolVersion.Major == serverProtocolVersion.Major && + clientProtocolVersion.Minor >= serverProtocolVersion.Minor)) { // Throw for protocol versions 2.x that don't support disconnect/reconnect. - // Protocol: < 2.2 + // Client protocol: < 2.2 PSRemotingDataStructureException reasonOfFailure = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnNegotiation, RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME, @@ -1053,47 +996,23 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil RemotingConstants.ProtocolVersion); throw reasonOfFailure; } + + // All other minor versions are supported and the server returns its full capability. + // Client protocol: v2.4, v2.5 ... } else { - // Win10 server can support Win8 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is Win8 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - // Win8, Win10 server can support Win7 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) + if (clientProtocolVersion == RemotingConstants.ProtocolVersion_2_0 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_1 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { - // - report that server is Win7 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RTM; + // We support the those client versions and report the server as the same version to the client. + serverProtocolVersion = clientProtocolVersion; Context.ServerCapability.ProtocolVersion = serverProtocolVersion; } - - // Win7, Win8, Win10 server can support Win7 RC client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RC && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is RC version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RC; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - if (!((clientProtocolVersion.Major == serverProtocolVersion.Major) && - (clientProtocolVersion.Minor >= serverProtocolVersion.Minor))) + else if (!(clientProtocolVersion.Major == serverProtocolVersion.Major && + clientProtocolVersion.Minor >= serverProtocolVersion.Minor)) { PSRemotingDataStructureException reasonOfFailure = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed, diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs index b943444dd0a..67d47ea404c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs @@ -31,14 +31,15 @@ namespace System.Management.Automation.Remoting /// internal class ServerRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] - private static PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine"); + [TraceSource("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] + private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine"); - private ServerRemoteSession _session; - private object _syncObject; + private readonly ServerRemoteSession _session; + private readonly object _syncObject; - private Queue _processPendingEventsQueue + private readonly Queue _processPendingEventsQueue = new Queue(); + // whether some thread is actively processing events // in a loop. If this is set then other threads // should simply add to the queue and not attempt @@ -47,7 +48,7 @@ private Queue _processPendingEventsQueue // and processed private bool _eventsInProcess = false; - private EventHandler[,] _stateMachineHandle; + private readonly EventHandler[,] _stateMachineHandle; private RemoteSessionState _state; /// @@ -346,7 +347,6 @@ private void DoNegotiationPending(object sender, RemoteSessionStateMachineEventA /// If the parameter is not NegotiationReceived event or it does not hold the /// client negotiation packet. /// - private void DoNegotiationReceived(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { using (s_trace.TraceEventHandlers()) @@ -386,7 +386,6 @@ private void DoNegotiationReceived(object sender, RemoteSessionStateMachineEvent /// /// If the parameter is null. /// - private void DoNegotiationSending(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { if (fsmEventArg == null) @@ -413,7 +412,6 @@ private void DoNegotiationSending(object sender, RemoteSessionStateMachineEventA /// /// If the parameter is null. /// - private void DoNegotiationCompleted(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { using (s_trace.TraceEventHandlers()) @@ -441,7 +439,6 @@ private void DoNegotiationCompleted(object sender, RemoteSessionStateMachineEven /// /// If the parameter is null. /// - private void DoEstablished(object sender, RemoteSessionStateMachineEventArgs fsmEventArg) { using (s_trace.TraceEventHandlers()) @@ -916,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 @@ -987,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); @@ -1005,7 +996,7 @@ private void HandleKeyExchangeTimeout(object sender) /// It can also be used for graceful shutdown of the server process, which is not currently /// implemented. /// - private void CleanAll() + private static void CleanAll() { } diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs index 1e72be279c7..06fbca348cc 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotingprotocolimplementation.cs @@ -12,9 +12,9 @@ namespace System.Management.Automation.Remoting /// internal class ServerRemoteSessionDSHandlerImpl : ServerRemoteSessionDataStructureHandler { - private AbstractServerSessionTransportManager _transportManager; - private ServerRemoteSessionDSHandlerStateMachine _stateMachine; - private ServerRemoteSession _session; + private readonly AbstractServerSessionTransportManager _transportManager; + private readonly ServerRemoteSessionDSHandlerStateMachine _stateMachine; + private readonly ServerRemoteSession _session; internal override AbstractServerSessionTransportManager TransportManager { @@ -242,4 +242,3 @@ internal override void RaiseDataReceivedEvent(RemoteDataEventArgs dataArg) #endregion Overrides } } - diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index a7ace47929d..027c4886380 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -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; @@ -93,8 +95,8 @@ internal static BindingRestrictions PSGetMethodArgumentRestriction(this DynamicM if (baseValue != null && baseValue.GetType() == typeof(object[])) { var effectiveArgType = Adapter.EffectiveArgumentType(obj.Value); - var methodInfo = !(effectiveArgType == typeof(object[])) - ? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogenousArray.MakeGenericMethod(effectiveArgType.GetElementType()) + var methodInfo = effectiveArgType != typeof(object[]) + ? CachedReflectionInfo.PSInvokeMemberBinder_IsHomogeneousArray.MakeGenericMethod(effectiveArgType.GetElementType()) : CachedReflectionInfo.PSInvokeMemberBinder_IsHeterogeneousArray; BindingRestrictions restrictions; @@ -444,7 +446,7 @@ internal static DynamicMetaObject UpdateComRestrictionsForPsObject(this DynamicM } } - internal class BinderUtils + internal static class BinderUtils { internal static BindingRestrictions GetVersionCheck(DynamicMetaObjectBinder binder, int expectedVersionNumber) { @@ -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,13 +552,13 @@ 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)); } internal static Expression MaybeDebase(DynamicMetaObjectBinder binder, Func generator, DynamicMetaObject target) { - if (!(target.Value is PSObject)) + if (target.Value is not PSObject) { return generator(target.Expression); } @@ -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); } @@ -737,7 +739,11 @@ private static IEnumerator NotEnumerableRule(CallSite site, object obj) return null; } - if (!(obj is PSObject) && !(obj is IEnumerable) && !(obj is IEnumerator) && !(obj is DataTable) && !Marshal.IsComObject(obj)) + if (obj is not PSObject + && obj is not IEnumerable + && obj is not IEnumerator + && obj is not DataTable + && !Marshal.IsComObject(obj)) { return null; } @@ -776,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(); @@ -821,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); } @@ -832,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(); @@ -885,7 +891,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje restrictions)).WriteToDebugLog(this); } - bool needsToDispose = !(PSObject.Base(target.Value) is IEnumerator); + bool needsToDispose = PSObject.Base(target.Value) is not IEnumerator; return (new DynamicMetaObject( Expression.Call(CachedReflectionInfo.EnumerableOps_WriteEnumerableToPipe, enumerable.Expression, @@ -926,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; @@ -951,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); } } @@ -1039,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(); @@ -1110,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(); @@ -1136,7 +1142,7 @@ public override DynamicMetaObject Bind(DynamicMetaObject target, DynamicMetaObje Diagnostics.Assert(pipelineResult != null, "Pipeline result is always an IList"); var ilistExpr = target.Expression; - if (!(typeof(IList) == ilistExpr.Type)) + if (typeof(IList) != ilistExpr.Type) { ilistExpr = Expression.Convert(ilistExpr, typeof(IList)); } @@ -1171,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) { @@ -1198,7 +1204,7 @@ internal static PSInvokeDynamicMemberBinder Get(CallInfo callInfo, TypeDefinitio { PSInvokeDynamicMemberBinder result; - var classScope = classScopeAst != null ? classScopeAst.Type : null; + var classScope = classScopeAst?.Type; lock (s_binderCache) { var key = Tuple.Create(callInfo, constraints, propertySetter, @static, classScope); @@ -1282,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()); @@ -1292,7 +1298,7 @@ internal static PSGetDynamicMemberBinder Get(TypeDefinitionAst classScope, bool PSGetDynamicMemberBinder binder; lock (s_binderCache) { - var type = classScope != null ? classScope.Type : null; + var type = classScope?.Type; var tuple = Tuple.Create(type, @static); if (!s_binderCache.TryGetValue(tuple, out binder)) { @@ -1392,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()); @@ -1402,7 +1408,7 @@ internal static PSSetDynamicMemberBinder Get(TypeDefinitionAst classScope, bool PSSetDynamicMemberBinder binder; lock (s_binderCache) { - var type = classScope != null ? classScope.Type : null; + var type = classScope?.Type; var tuple = Tuple.Create(type, @static); if (!s_binderCache.TryGetValue(tuple, out binder)) { @@ -1484,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 @@ -1604,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(); @@ -1649,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, @@ -1722,7 +1728,8 @@ public override DynamicMetaObject FallbackCreateInstance(DynamicMetaObject targe { var members = attributeType.GetMember(name, MemberTypes.Field | MemberTypes.Property, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); - if (members.Length != 1 || !(members[0] is PropertyInfo || members[0] is FieldInfo)) + if (members.Length != 1 + || (members[0] is not PropertyInfo && members[0] is not FieldInfo)) { return target.ThrowRuntimeError(args, BindingRestrictions.Empty, "PropertyNotFoundForType", ParserStrings.PropertyNotFoundForType, Expression.Constant(name), @@ -1784,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(); @@ -1820,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(); @@ -1858,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; @@ -2011,7 +2018,7 @@ private static object IntRule(CallSite site, object obj) private static object ObjectRule(CallSite site, object obj) { - if (!(obj is ValueType) && !(obj is PSObject)) { return obj; } + if (obj is not ValueType && obj is not PSObject) { return obj; } return ((CallSite>)site).Update(site, obj); } @@ -2030,36 +2037,39 @@ private static object NullRule(CallSite site, object obj) internal static bool IsValueTypeMutable(Type type) { - if (type.IsPrimitive || type.IsEnum) + // First, check for enums/primitives and compiler-defined attributes. + if (type.IsPrimitive + || type.IsEnum + || type.IsDefined(typeof(System.Runtime.CompilerServices.IsReadOnlyAttribute), inherit: false)) { return false; } - // If there are any fields, the type is mutable. - if (type.GetFields(BindingFlags.Public | BindingFlags.Instance).Length > 0) + // If the builtin attribute is not present, check for a custom attribute from by the compiler. If the + // library targets netstandard2.0, the compiler can't be sure the attribute will be provided by the runtime, + // and defines its own attribute of the same name during compilation. To account for this, we must check the + // type by name, not by reference. + foreach (object attribute in type.GetCustomAttributes(inherit: false)) { - return true; + if (attribute.GetType().FullName.Equals( + "System.Runtime.CompilerServices.IsReadOnlyAttribute", + StringComparison.Ordinal)) + { + return false; + } } - // If there are any properties with setters, the type is mutable. - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); - for (int index = 0; index < properties.Length; index++) + // Fallback: check all fields (public + private) to verify whether they're all readonly. + // If any field is not readonly, the value type is potentially mutable. + foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { - var property = properties[index]; - if (property.CanWrite) + if (!field.IsInitOnly) { return true; } } - // If there are any methods other than the property getters, the type might - // be mutable, so assume the type is mutable. - var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance); - if (methods.Length != properties.Length) - { - return true; - } - + // If all fields are init-only (read-only), then the value type is immutable. return false; } @@ -2081,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()); @@ -2108,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 @@ -2233,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() @@ -2651,7 +2664,7 @@ private DynamicMetaObject BinaryAdd(DynamicMetaObject target, DynamicMetaObject return new DynamicMetaObject(arg.Expression.Cast(typeof(object)), target.CombineRestrictions(arg)); } - if (target.LimitType.IsNumericOrPrimitive() && !(target.LimitType == typeof(char))) + if (target.LimitType.IsNumericOrPrimitive() && target.LimitType != typeof(char)) { var numericArg = GetArgAsNumericOrPrimitive(arg, target.LimitType); if (numericArg != null) @@ -2705,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 @@ -3075,7 +3096,7 @@ private DynamicMetaObject BinaryEqualityComparison(DynamicMetaObject target, Dyn var targetExpr = target.Expression.Cast(typeof(string)); // Doing a string comparison no matter what. - var argExpr = !(arg.LimitType == typeof(string)) + var argExpr = arg.LimitType != typeof(string) ? DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), arg.Expression, ExpressionCache.GetExecutionContextFromTLS) : arg.Expression.Cast(typeof(string)); @@ -3179,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, @@ -3199,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, @@ -3221,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, @@ -3243,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) @@ -3253,7 +3274,7 @@ private DynamicMetaObject BinaryComparison(DynamicMetaObject target, DynamicMeta var targetExpr = target.Expression.Cast(typeof(string)); // Doing a string comparison no matter what. - var argExpr = !(arg.LimitType == typeof(string)) + var argExpr = arg.LimitType != typeof(string) ? DynamicExpression.Dynamic(PSToStringBinder.Get(), typeof(string), arg.Expression, ExpressionCache.GetExecutionContextFromTLS) : arg.Expression.Cast(typeof(string)); @@ -3403,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; @@ -3482,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) @@ -3719,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; @@ -3784,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() @@ -3811,7 +3836,7 @@ internal static DynamicMetaObject ThrowNoConversion(DynamicMetaObject target, Ty target.Expression.Cast(typeof(object)), Expression.Constant(toType, typeof(Type))); - if (!(binder.ReturnType == typeof(void))) + if (binder.ReturnType != typeof(void)) { expr = Expression.Block(expr, Expression.Default(binder.ReturnType)); } @@ -3836,7 +3861,7 @@ internal static Expression ConvertToByRefLikeTypeViaCasting(DynamicMetaObject ar var baseObject = PSObject.Base(argument.Value); // Source value cannot be null or AutomationNull, and it cannot be a pure PSObject. - if (baseObject != null && !(baseObject is PSObject)) + if (baseObject != null && baseObject is not PSObject) { Type fromType = baseObject.GetType(); ConversionRank rank = ConversionRank.None; @@ -3918,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>(); @@ -3953,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() @@ -3975,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); } @@ -4076,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); } @@ -4259,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) @@ -4305,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) @@ -4469,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)); } @@ -4509,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>(); @@ -4542,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() @@ -4564,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); } @@ -4673,7 +4703,9 @@ private DynamicMetaObject InvokeIndexer( } } - if (paramLength == 2 && setterParams[0].ParameterType == typeof(int) && !(target.Value is IDictionary)) + if (paramLength == 2 + && setterParams[0].ParameterType == typeof(int) + && target.Value is not IDictionary) { // PowerShell supports negative indexing for some types (specifically, those with a single // int parameter to the indexer, and also have either a Length or Count property.) For @@ -4784,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)); @@ -4806,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, @@ -4849,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]; @@ -4877,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) { @@ -4901,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) { @@ -4997,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) { @@ -5028,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) { @@ -5051,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) { @@ -5064,7 +5096,7 @@ internal static void TypeTableMemberPossiblyUpdated(string memberName) public static PSGetMemberBinder Get(string memberName, TypeDefinitionAst classScope, bool @static) { - return Get(memberName, classScope != null ? classScope.Type : null, @static, false); + return Get(memberName, classScope?.Type, @static, false); } public static PSGetMemberBinder Get(string memberName, Type classScope, bool @static) @@ -5099,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) @@ -5136,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) @@ -5163,7 +5200,7 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy // Check if this is a COM Object DynamicMetaObject result; - if (ComInterop.ComBinder.TryBindGetMember(this, target, out result)) + if (ComInterop.ComBinder.TryBindGetMember(this, target, out result, delayInvocation: false)) { result = new DynamicMetaObject(WrapGetMemberInTry(result.Expression), result.Restrictions); return result.WriteToDebugLog(this); @@ -5261,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 { @@ -5325,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) @@ -5397,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. @@ -5485,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) + { + return null; + } + + if (context.LanguageMode == PSLanguageMode.ConstrainedLanguage && + !IsAllowedInConstrainedLanguage(targetValue, name, isStatic)) { - if (!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; @@ -5615,14 +5662,13 @@ 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; ConsolidatedString typenames = null; var context = LocalPipeline.GetExecutionContextFromTLS(); - var typeTable = context != null ? context.TypeTable : null; + var typeTable = context?.TypeTable; if (hasTypeTableMember) { @@ -5677,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) @@ -5719,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)); } @@ -5730,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)); } @@ -5738,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); } @@ -5758,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; } @@ -5769,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); } } @@ -5835,11 +5872,8 @@ internal static object GetAdaptedValue(object obj, string member) } } - var adapterSet = PSObject.GetMappedAdapter(obj, context != null ? context.TypeTable : null); - if (memberInfo == null) - { - memberInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, member); - } + var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); + memberInfo ??= adapterSet.OriginalAdapter.BaseGetMember(obj, member); if (memberInfo == null && adapterSet.DotNetAdapter != null) { @@ -5874,7 +5908,7 @@ internal static bool IsTypeNameSame(object value, string typeName) internal static TypeTable GetTypeTableFromTLS() { var executionContext = LocalPipeline.GetExecutionContextFromTLS(); - return executionContext != null ? executionContext.TypeTable : null; + return executionContext?.TypeTable; } internal static bool TryGetInstanceMember(object value, string memberName, out PSMemberInfo memberInfo) @@ -5926,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) { @@ -5957,7 +5991,7 @@ private static readonly Dictionary public static PSSetMemberBinder Get(string memberName, TypeDefinitionAst classScopeAst, bool @static) { - var classScope = classScopeAst != null ? classScopeAst.Type : null; + var classScope = classScopeAst?.Type; return Get(memberName, classScope, @static); } @@ -5988,10 +6022,15 @@ 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 Expression GetTransformedExpression(IEnumerable transformationAttributes, Expression originalExpression) + private static Expression GetTransformedExpression(IEnumerable transformationAttributes, Expression originalExpression) { if (transformationAttributes == null) { @@ -6155,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) { @@ -6255,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); @@ -6279,7 +6318,7 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy if (value.Value == null) { expr = Expression.Block( - Expression.Assign(lhs, this.GetTransformedExpression(argumentTransformationAttributes, Expression.Constant(null, lhsType))), + Expression.Assign(lhs, GetTransformedExpression(argumentTransformationAttributes, Expression.Constant(null, lhsType))), ExpressionCache.NullConstant); } else @@ -6288,7 +6327,7 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy Expression assignmentExpression; if (transformationNeeded) { - var transformedExpr = this.GetTransformedExpression(argumentTransformationAttributes, value.Expression); + var transformedExpr = GetTransformedExpression(argumentTransformationAttributes, value.Expression); assignmentExpression = DynamicExpression.Dynamic(PSConvertBinder.Get(nullableUnderlyingType), nullableUnderlyingType, transformedExpr); } else @@ -6310,7 +6349,7 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy if (transformationNeeded) { assignedValue = DynamicExpression.Dynamic(PSConvertBinder.Get(lhsType), lhsType, - this.GetTransformedExpression(argumentTransformationAttributes, value.Expression)); + GetTransformedExpression(argumentTransformationAttributes, value.Expression)); } else { @@ -6439,11 +6478,8 @@ internal static object SetAdaptedValue(object obj, string member, object value) } } - var adapterSet = PSObject.GetMappedAdapter(obj, context != null ? context.TypeTable : null); - if (memberInfo == null) - { - memberInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, member); - } + var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); + memberInfo ??= adapterSet.OriginalAdapter.BaseGetMember(obj, member); if (memberInfo == null && adapterSet.DotNetAdapter != null) { @@ -6498,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, @@ -6509,7 +6560,7 @@ internal enum MethodInvocationType NonVirtual, } - private class KeyComparer : IEqualityComparer + private sealed class KeyComparer : IEqualityComparer { public bool Equals(PSInvokeMemberBinderKeyType x, PSInvokeMemberBinderKeyType y) { @@ -6595,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)) @@ -6640,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); } @@ -6685,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), @@ -6696,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 @@ -6705,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) { @@ -6728,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 @@ -6738,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); @@ -6764,7 +6822,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, // If we get here, then the target value should have 'isDeserialized == false', otherwise we cannot get a .NET methodInfo // from _getMemberBinder.GetPSMemberInfo(). This is because when 'isDeserialized' is true, we use the PSObject to find the // corresponding Adapter -- PSObjectAdapter, which cannot be optimized. - Diagnostics.Assert(psObj.IsDeserialized == false, + Diagnostics.Assert(!psObj.IsDeserialized, "isDeserialized should be false, because if not, we cannot get a .NET method/parameterizedProperty from GetPSMemberInfo"); restrictions = restrictions.Merge(BindingRestrictions.GetExpressionRestriction( @@ -6790,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); } @@ -6828,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); } @@ -6905,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 @@ -6957,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)) )); @@ -6985,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, @@ -7067,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) { @@ -7091,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 { @@ -7111,16 +7186,35 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, allowCastingToByRefLikeType: false, temps, initTemps); + argExprs[i] = arg; + argsToLog.Add(arg); } } else if (i >= args.Length) { - Diagnostics.Assert(parameters[i].IsOptional, + // We don't log the default value for an optional parameter, as it's not specified by the user. + Diagnostics.Assert( + parameters[i].IsOptional, "if there are too few arguments, FindBestMethod should only succeed if parameters are optional"); + var argValue = parameters[i].DefaultValue; if (argValue == null) { + if (parameterType.IsByRef) + { + // When the default value is null for a ByRef parameter (e.g. an optional `in` parameter + // using `default`), expression trees cannot create Expression.Default for the T& type. + // In that case we switch to the element type and use Default(TElement) instead. + parameterType = parameterType.GetElementType(); + } + argExprs[i] = Expression.Default(parameterType); + } + else if (!parameters[i].HasDefaultValue && parameterType != typeof(object) && argValue == Type.Missing) + { + // 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 @@ -7135,7 +7229,7 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, { if (parameterType.IsByRef) { - if (!(args[i].Value is PSReference)) + if (args[i].Value is not PSReference) { return Compiler.CreateThrow(typeof(object), typeof(MethodException), new[] { typeof(string), typeof(Exception), typeof(string), typeof(object[]) }, @@ -7148,17 +7242,25 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, var psRefValue = Expression.Property(args[i].Expression.Cast(typeof(PSReference)), CachedReflectionInfo.PSReference_Value); initTemps.Add(Expression.Assign(temp, psRefValue.Convert(temp.Type))); copyOutTemps.Add(Expression.Assign(psRefValue, temp.Cast(typeof(object)))); + argExprs[i] = temp; + argsToLog.Add(temp); } else { - argExprs[i] = args[i].CastOrConvertMethodArgument( + var convertedArg = args[i].CastOrConvertMethodArgument( parameterType, paramName, mi.Name, allowCastingToByRefLikeType, temps, initTemps); + + argExprs[i] = convertedArg; + // If the converted arg is a byref-like type, then we log the original arg. + argsToLog.Add(convertedArg.Type.IsByRefLike + ? args[i].Expression + : convertedArg); } } } @@ -7175,7 +7277,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 { @@ -7192,7 +7294,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 { @@ -7204,6 +7306,12 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, } } + // We need to add one expression to log the .NET invocation before actually invoking: + // - Log method invocation to AMSI Notifications (can throw PSSecurityException) + // - Invoke method + string targetName = mi.ReflectedType?.FullName ?? string.Empty; + string methodName = mi.Name is ".ctor" ? "new" : mi.Name; + if (temps.Count > 0) { if (call.Type != typeof(void) && copyOutTemps.Count > 0) @@ -7214,22 +7322,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)); @@ -7238,14 +7351,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; } @@ -7330,7 +7440,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 { @@ -7345,18 +7455,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) @@ -7384,17 +7498,21 @@ 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) { var context = LocalPipeline.GetExecutionContextFromTLS(); - var adapterSet = PSObject.GetMappedAdapter(obj, context != null ? context.TypeTable : null); + var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); var methodInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, methodName) as PSMethodInfo; if (methodInfo == null && adapterSet.DotNetAdapter != null) { @@ -7410,7 +7528,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object // As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like // ([pscustomobject]@{ foo = 'bar' }).Foreach({$_}) // ([pscustomobject]@{ foo = 'bar' }).Where({1}) - if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase)) + if (s_whereSearchValues.Contains(methodName)) { var enumerator = (new object[] { obj }).GetEnumerator(); switch (args.Length) @@ -7426,10 +7544,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, @@ -7439,7 +7570,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object internal static object InvokeAdaptedSetMember(object obj, string methodName, object[] args, object valueToSet) { var context = LocalPipeline.GetExecutionContextFromTLS(); - var adapterSet = PSObject.GetMappedAdapter(obj, context != null ? context.TypeTable : null); + var adapterSet = PSObject.GetMappedAdapter(obj, context?.TypeTable); var methodInfo = adapterSet.OriginalAdapter.BaseGetMember(obj, methodName); if (methodInfo == null && adapterSet.DotNetAdapter != null) { @@ -7489,6 +7620,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 } @@ -7499,7 +7679,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) @@ -7559,13 +7739,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()); } @@ -7626,10 +7810,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); @@ -7677,7 +7872,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) @@ -7723,7 +7918,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()); } @@ -7733,8 +7928,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)); } @@ -7745,7 +7940,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 819588225ba..f4829bc3547 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, } @@ -105,7 +105,7 @@ private void InitializeMetadata() { if (attribute is CmdletBindingAttribute c) { - cmdletBindingAttribute = cmdletBindingAttribute ?? c; + cmdletBindingAttribute ??= c; } else if (attribute is DebuggerHiddenAttribute) { @@ -188,13 +188,15 @@ 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() { - var scriptBlockAst = Ast as ScriptBlockAst; - if (scriptBlockAst == null) + if (Ast is not ScriptBlockAst scriptBlockAst) { // Checks are only needed at the top level. return; @@ -248,6 +250,7 @@ bool IsScriptBlockInFactASafeHashtable() if (scriptBlockAst.BeginBlock != null || scriptBlockAst.ProcessBlock != null + || scriptBlockAst.CleanBlock != null || scriptBlockAst.ParamBlock != null || scriptBlockAst.DynamicParamBlock != null || scriptBlockAst.ScriptRequirements != null @@ -263,14 +266,12 @@ bool IsScriptBlockInFactASafeHashtable() return false; } - PipelineAst pipelineAst = endBlock.Statements[0] as PipelineAst; - if (pipelineAst == null) + if (endBlock.Statements[0] is not PipelineAst pipelineAst) { return false; } - HashtableAst hashtableAst = pipelineAst.GetPureExpression() as HashtableAst; - if (hashtableAst == null) + if (pipelineAst.GetPureExpression() is not HashtableAst hashtableAst) { return false; } @@ -310,20 +311,39 @@ private IParameterMetadataProvider DelayParseScriptText() } internal Type LocalsMutableTupleType { get; set; } + internal Type UnoptimizedLocalsMutableTupleType { get; set; } + internal Func LocalsMutableTupleCreator { get; set; } + internal Func UnoptimizedLocalsMutableTupleCreator { get; set; } + internal Dictionary NameToIndexMap { get; set; } + #region Named Blocks + internal Action DynamicParamBlock { get; set; } + internal Action UnoptimizedDynamicParamBlock { get; set; } + internal Action BeginBlock { get; set; } + internal Action UnoptimizedBeginBlock { get; set; } + internal Action ProcessBlock { get; set; } + internal Action UnoptimizedProcessBlock { get; set; } + internal Action EndBlock { get; set; } + 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; @@ -335,21 +355,22 @@ private IParameterMetadataProvider DelayParseScriptText() private bool? _isProductCode; internal bool DebuggerHidden { get; set; } + internal bool DebuggerStepThrough { get; set; } + internal Guid Id { get; private set; } internal bool HasLogged { get; set; } + internal bool SkipLogging { get; set; } - internal bool IsFilter { get; private set; } + + internal bool IsFilter { get; } internal bool IsProductCode { get { - if (_isProductCode == null) - { - _isProductCode = SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); - } + _isProductCode ??= SecuritySupport.IsProductBinary(((Ast)_ast).Extent.File); return _isProductCode.Value; } @@ -428,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; } } @@ -442,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); } } @@ -518,13 +539,12 @@ public override string ToString() } } - [Serializable] - public partial class ScriptBlock : ISerializable + public partial class ScriptBlock { private readonly CompiledScriptBlockData _scriptBlockData; - internal ScriptBlock(IParameterMetadataProvider ast, bool isFilter) : - this(new CompiledScriptBlockData(ast, isFilter)) + internal ScriptBlock(IParameterMetadataProvider ast, bool isFilter) + : this(new CompiledScriptBlockData(ast, isFilter)) { } @@ -552,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) { } @@ -600,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; } @@ -691,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, @@ -726,10 +732,10 @@ internal PowerShell GetPowerShellImpl( internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, object[] args) { var pipelineAst = GetSimplePipeline( - resourceString => { throw PSTraceSource.NewInvalidOperationException(resourceString); }); + resourceString => throw PSTraceSource.NewInvalidOperationException(resourceString)); Diagnostics.Assert(pipelineAst != null, "This should be checked by GetSimplePipeline"); - if (!(pipelineAst.PipelineElements[0] is CommandAst)) + if (pipelineAst.PipelineElements[0] is not CommandAst) { throw PSTraceSource.NewInvalidOperationException(AutomationExceptions.CantConvertEmptyPipeline); } @@ -739,9 +745,9 @@ internal SteppablePipeline GetSteppablePipelineImpl(CommandOrigin commandOrigin, private PipelineAst GetSimplePipeline(Func errorHandler) { - errorHandler = errorHandler ?? (_ => null); + errorHandler ??= (static _ => null); - if (HasBeginBlock || HasProcessBlock) + if (HasBeginBlock || HasProcessBlock || HasCleanBlock) { return errorHandler(AutomationExceptions.CanConvertOneClauseOnly); } @@ -763,8 +769,7 @@ private PipelineAst GetSimplePipeline(Func errorHandler) return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap); } - var pipeAst = statements[0] as PipelineAst; - if (pipeAst == null) + if (statements[0] is not PipelineAst pipeAst) { return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline); } @@ -833,7 +838,7 @@ internal bool SkipLogging set { _scriptBlockData.SkipLogging = value; } } - internal Assembly AssemblyDefiningPSTypes { set; get; } + internal Assembly AssemblyDefiningPSTypes { get; set; } internal HelpInfo GetHelpInfo( ExecutionContext context, @@ -880,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( @@ -963,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)) @@ -980,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"); @@ -988,23 +1001,17 @@ 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 ? false : createLocalScope; + bool runOptimized = context._debuggingMode <= 0 && createLocalScope; var codeToInvoke = GetCodeToInvoke(ref runOptimized, clauseToInvoke); if (codeToInvoke == null) { 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); @@ -1030,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. @@ -1046,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; @@ -1184,7 +1204,7 @@ internal void InvokeWithPipeImpl( _sequencePoints = SequencePoints, }; - ScriptBlock.LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); + LogScriptBlockStart(this, context.CurrentRunspace.InstanceId); try { @@ -1192,7 +1212,7 @@ internal void InvokeWithPipeImpl( } finally { - ScriptBlock.LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); + LogScriptBlockEnd(this, context.CurrentRunspace.InstanceId); } } catch (TargetInvocationException tie) @@ -1351,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); } @@ -1368,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; } @@ -1381,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; } @@ -1427,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; @@ -1716,13 +1740,13 @@ private static bool GetAndValidateEncryptionRecipients( return true; } - private static object s_syncObject = new object(); + private static readonly object s_syncObject = new object(); private static string s_lastSeenCertificate = string.Empty; private static bool s_hasProcessedCertificate = false; private static CmsMessageRecipient[] s_encryptionRecipients = null; - private static Lazy s_sbLoggingSettingCache = new Lazy( - () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), + private static readonly Lazy s_sbLoggingSettingCache = new Lazy( + static () => Utils.GetPolicySetting(Utils.SystemWideThenCurrentUserConfig), isThreadSafe: true); // Reset any static caches if the certificate has changed @@ -1778,7 +1802,7 @@ internal static string CheckSuspiciousContent(Ast scriptBlockAst) return null; } - private class SuspiciousContentChecker + private static class SuspiciousContentChecker { // Based on a (bad) random number generator, but good enough // for our simple needs. @@ -1946,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. @@ -2001,7 +2025,7 @@ public static string Match(string text) uint h = text[i]; if (h >= 'A' && h <= 'Z') { - h = h | 0x20; // ToLower + h |= 0x20; // ToLower } else if (!((h >= 'a' && h <= 'z') || h == '-')) { @@ -2011,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: @@ -2032,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; + } } } @@ -2136,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 @@ -2185,18 +2183,20 @@ internal sealed class PSScriptCmdlet : PSCmdlet, IDynamicParameters, IDisposable private readonly bool _fromScriptFile; private readonly bool _useLocalScope; private readonly bool _runOptimized; - private bool _rethrowExitException; - private MshCommandRuntime _commandRuntime; + private readonly bool _rethrowExitException; 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; _useLocalScope = useNewScope; _fromScriptFile = fromScriptFile; - _runOptimized = _scriptBlock.Compile(optimized: context._debuggingMode > 0 ? false : useNewScope); + _runOptimized = _scriptBlock.Compile(optimized: context._debuggingMode <= 0 && useNewScope); _localsTuple = _scriptBlock.MakeLocalsTuple(_runOptimized); _localsTuple.SetAutomaticVariable(AutomaticVariable.PSCmdlet, this, context); _scriptBlock.SetPSScriptRootAndPSCommandPath(_localsTuple, context); @@ -2280,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(); @@ -2292,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; @@ -2345,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; @@ -2460,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); @@ -2507,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 de923e2df8b..53c40ef3810 100644 --- a/src/System.Management.Automation/engine/runtime/MutableTuple.cs +++ b/src/System.Management.Automation/engine/runtime/MutableTuple.cs @@ -149,6 +149,7 @@ public void SetValue(int index, object value) } protected abstract object GetValueImpl(int index); + protected abstract void SetValueImpl(int index, object value); /// @@ -280,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) @@ -297,7 +302,10 @@ public static int GetSize(Type tupleType) continue; } - if (t == typeof(DynamicNull)) continue; + if (t == typeof(DynamicNull)) + { + continue; + } count++; } @@ -368,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; @@ -439,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)); } } @@ -503,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) @@ -511,7 +519,7 @@ private static int PowerOfTwoRound(int value) int res = 1; while (value > res) { - res = res << 1; + res <<= 1; } return res; @@ -539,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); } @@ -563,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 d340f8c2667..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]; @@ -52,7 +61,10 @@ internal static T[] Multiply(T[] array, uint times) if (times == 0 || array.Length == 0) { - return new T[0]; // don't use Utils.EmptyArray, always return a new array +#pragma warning disable CA1825 // Avoid zero-length array allocations + // Don't use Array.Empty(); always return a new instance. + return new T[0]; +#pragma warning restore CA1825 // Avoid zero-length array allocations } var context = LocalPipeline.GetExecutionContextFromTLS(); @@ -231,7 +243,7 @@ internal static string IndexStringMessage(object index) // Convert this index into something printable (we hope)... string msgString = PSObject.ToString(null, index, ",", null, null, true, true); if (msgString.Length > 20) - msgString = msgString.Substring(0, 20) + " ..."; + msgString = string.Concat(msgString.AsSpan(0, 20), " ..."); return msgString; } diff --git a/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs b/src/System.Management.Automation/engine/runtime/Operations/ClassOps.cs index 7b06697afa1..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) @@ -284,7 +284,7 @@ public static void ValidateSetProperty(Type type, string propertyName, object va { var validateAttributes = type.GetProperty(propertyName).GetCustomAttributes(); var executionContext = LocalPipeline.GetExecutionContextFromTLS(); - var engineIntrinsics = executionContext == null ? null : executionContext.EngineIntrinsics; + var engineIntrinsics = executionContext?.EngineIntrinsics; foreach (var validateAttribute in validateAttributes) { validateAttribute.InternalValidate(value, engineIntrinsics); @@ -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 7077ebeacbe..d584666ab62 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; @@ -104,7 +117,7 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, else { var commandName = command as string ?? PSObject.ToStringParser(context, command); - invocationName = invocationName ?? commandName; + invocationName ??= commandName; if (string.IsNullOrEmpty(commandName)) { @@ -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]; @@ -174,7 +188,7 @@ private static CommandProcessorBase AddCommand(PipelineProcessor pipe, } } - if (cpi.ArgumentSplatted) + if (cpi.ArgumentToBeSplatted) { foreach (var splattedCpi in Splat(cpi.ArgumentValue, cpi.ArgumentAst)) { @@ -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: @@ -338,8 +367,13 @@ internal static IEnumerable Splat(object splattedValue } yield return CommandParameterInternal.CreateParameterWithArgument( - splatAst, parameterName, parameterText, - splatAst, parameterValue, false); + parameterAst: splatAst, + parameterName: parameterName, + parameterText: parameterText, + argumentAst: splatAst, + value: parameterValue, + spaceAfterParameter: false, + fromSplatting: true); } } else @@ -402,7 +436,7 @@ private static string GetParameterText(string parameterName) else { string whitespaces = parameterName.Substring(endPosition); - parameterText = "-" + parameterName.Substring(0, endPosition) + ":" + whitespaces; + parameterText = string.Concat("-", parameterName.AsSpan(0, endPosition), ":", whitespaces); } return parameterText; @@ -421,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) { @@ -444,7 +475,7 @@ internal static void InvokePipeline(object input, for (int i = 0; i < pipeElements.Length; i++) { - commandRedirection = commandRedirections != null ? commandRedirections[i] : null; + commandRedirection = commandRedirections?[i]; commandProcessor = AddCommand(pipelineProcessor, pipeElements[i], pipeElementAsts[i], commandRedirection, context); } @@ -514,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: @@ -546,10 +570,10 @@ internal static void InvokePipelineInBackground( } // Skip PowerShell magic variables - if (Regex.Match( + if (!Regex.Match( variableName, "^(global:){0,1}(PID|PSVersionTable|PSEdition|PSHOME|HOST|TRUE|FALSE|NULL)$", - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success == false) + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success) { updatedScriptblock.Append(scriptblockBodyString.AsSpan(position, v.Extent.StartOffset - pipelineOffset - position)); updatedScriptblock.Append("${using:"); @@ -563,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())); @@ -679,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()) { @@ -694,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)); } @@ -762,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); @@ -829,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) { @@ -853,7 +897,7 @@ protected CommandRedirection(RedirectionStream from) this.FromStream = from; } - internal RedirectionStream FromStream { get; private set; } + internal RedirectionStream FromStream { get; } internal abstract void Bind(PipelineProcessor pipelineProcessor, CommandProcessorBase commandProcessor, ExecutionContext context); @@ -918,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; } @@ -1027,16 +1071,18 @@ 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; private set; } + internal string File { get; } - internal bool Appending { get; private set; } + internal bool Appending { get; } private PipelineProcessor PipelineProcessor { get; set; } @@ -1044,6 +1090,25 @@ 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; + + context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo 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) @@ -1053,10 +1118,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; @@ -1067,10 +1129,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; @@ -1159,26 +1218,49 @@ 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 + CommandProcessorBase commandProcessor; + var name = context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _); - var cpi = CommandParameterInternal.CreateParameterWithArgument( - /*parameterAst*/null, "Filepath", "-Filepath:", - /*argumentAst*/null, File, - false); - commandProcessor.AddParameter(cpi); + if (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(); @@ -1194,19 +1276,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); } @@ -1215,17 +1300,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; @@ -1243,10 +1325,7 @@ private void Dispose(bool disposing) if (disposing) { - if (PipelineProcessor != null) - { - PipelineProcessor.Dispose(); - } + PipelineProcessor?.Dispose(); } _disposed = true; @@ -1275,8 +1354,7 @@ internal static void DefineFunction(ExecutionContext context, } catch (Exception exception) { - var rte = exception as RuntimeException; - if (rte == null) + if (exception is not RuntimeException rte) { throw ExceptionHandlingOps.ConvertToRuntimeException(exception, functionDefinitionAst.Extent); } @@ -1304,12 +1382,23 @@ internal ScriptBlock GetScriptBlock(ExecutionContext context, bool isFilter) Diagnostics.Assert(_scriptBlock == null || _scriptBlock.SessionStateInternal == null, "Cached script block should not hold on to session state"); - var result = (_scriptBlock ?? (_scriptBlock = new ScriptBlock(_ast, isFilter))).Clone(); + var result = (_scriptBlock ??= new ScriptBlock(_ast, isFilter)).Clone(); result.SessionStateInternal = context.EngineSessionState; return result; } } + 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) @@ -1375,7 +1464,7 @@ internal class CatchAll { } /// /// Represent a handler search result. /// - private class HandlerSearchResult + private sealed class HandlerSearchResult { internal HandlerSearchResult() { @@ -1414,7 +1503,7 @@ private static int[] RankExceptionTypes(Type[] types) if (types[length - 1].Equals(typeof(CatchAll))) { ranks[length - 1] = length - 1; - length = length - 1; + length -= 1; } // For each type check if it's a sub-class of any types after it. @@ -1443,7 +1532,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, @@ -1477,7 +1569,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; @@ -1574,23 +1666,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; } } @@ -1612,6 +1714,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; @@ -1722,10 +1827,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 { @@ -1917,12 +2019,9 @@ internal static void SetErrorVariables(IScriptExtent extent, RuntimeException rt InterpreterError.UpdateExceptionErrorRecordPosition(rte, extent); ErrorRecord errRec = rte.ErrorRecord.WrapException(rte); - if (!(rte is PipelineStoppedException)) + if (rte is not PipelineStoppedException) { - if (outputPipe != null) - { - outputPipe.AppendVariableList(VariableStreamKind.Error, errRec); - } + outputPipe?.AppendVariableList(VariableStreamKind.Error, errRec); context.AppendDollarError(errRec); } @@ -2297,8 +2396,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)) { @@ -2769,10 +2873,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; @@ -2851,6 +2953,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) { @@ -2975,8 +3082,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); } } @@ -3223,7 +3340,10 @@ internal static object Multiply(IEnumerator enumerator, uint times) if (originalList.Count == 0) { - return new object[0]; // don't use Utils.EmptyArray, always return a new array +#pragma warning disable CA1825 // Avoid zero-length array allocations + // Don't use Array.Empty(); always return a new instance. + return new object[0]; +#pragma warning restore CA1825 // Avoid zero-length array allocations } return ArrayOps.Multiply(originalList.ToArray(), times); @@ -3311,17 +3431,7 @@ internal static IEnumerator GetCOMEnumerator(object obj) { } - // We use ComEnumerator to enumerate COM collections because the following code doesn't work in .NET Core - // IEnumerator enumerator = targetValue as IEnumerator; - // if (enumerator != null) - // { - // enumerable.MoveNext(); - // ... - // } - // The call to 'MoveNext()' throws exception because COM is not fully supported in .NET Core. - // See https://github.com/dotnet/runtime/issues/21690 for more information. - // When COM support is fully back to .NET Core, we need to change back to directly use the type cast. - return ComEnumerator.Create(targetValue) ?? NonEnumerableObjectEnumerator.Create(obj); + return targetValue as IEnumerator ?? NonEnumerableObjectEnumerator.Create(obj); } internal static IEnumerator GetGenericEnumerator(IEnumerable enumerable) @@ -3500,10 +3610,7 @@ internal static void WriteEnumerableToPipe(IEnumerator enumerator, Pipe pipe, Ex if (dispose) { var disposable = enumerator as IDisposable; - if (disposable != null) - { - disposable.Dispose(); - } + disposable?.Dispose(); } } } @@ -3534,4 +3641,111 @@ internal static object[] GetSlice(IList list, int startIndex) return result; } } + + internal static class MemberInvocationLoggingOps + { +#if DEBUG + 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; + } + ); +#endif + + 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 DEBUG + if (DumpLogAMSIContent.Value) + { + Console.WriteLine("\n=== Amsi notification report content ==="); + Console.WriteLine(content); + } +#endif + + var success = AmsiUtils.ReportContent( + name: contentName, + content: content); + +#if DEBUG + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"=== Amsi notification report success: {success} ==="); + } +#endif + } + catch (PSSecurityException) + { + // ReportContent() will throw PSSecurityException if AMSI detects malware, which + // must be propagated. + throw; + } +#pragma warning disable CS0168 // variable declared but never used + catch (Exception ex) +#pragma warning restore CS0168 + { +#if DEBUG + if (DumpLogAMSIContent.Value) + { + Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!"); + } +#endif + } + } + } } diff --git a/src/System.Management.Automation/engine/runtime/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 e2409eba626..defd87662e8 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -16,6 +16,7 @@ internal class ScriptBlockToPowerShellChecker : AstVisitor private readonly HashSet _validVariables = new HashSet(StringComparer.OrdinalIgnoreCase); internal ScriptBlockAst ScriptBeingConverted { get; set; } + internal bool UsesParameter { get; private set; } internal bool HasUsingExpr { get; private set; } @@ -171,13 +172,16 @@ internal static void ThrowError(ScriptBlockToPowerShellNotSupportedException ex, } } - internal class UsingExpressionAstSearcher : AstSearcher + internal sealed class UsingExpressionAstSearcher : AstSearcher { - internal static IEnumerable FindAllUsingExpressionExceptForWorkflow(Ast ast) + internal static IEnumerable FindAllUsingExpressions(Ast ast) { Diagnostics.Assert(ast != null, "caller to verify arguments"); - var searcher = new UsingExpressionAstSearcher(astParam => astParam is UsingExpressionAst, stopOnFirst: false, searchNestedScriptBlocks: true); + var searcher = new UsingExpressionAstSearcher( + callback: astParam => astParam is UsingExpressionAst, + stopOnFirst: false, + searchNestedScriptBlocks: true); ast.InternalVisit(searcher); return searcher.Results; } @@ -203,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; @@ -226,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 @@ -312,6 +313,156 @@ internal static PowerShell Convert(ScriptBlockAst body, } } + /// + /// Get using values as dictionary for the Foreach-Object parallel cmdlet. + /// Ignore any using expressions that are associated with inner nested Foreach-Object parallel calls, + /// since they are only effective in the nested call scope and not the current outer scope. + /// + /// Scriptblock to search. + /// True when input is trusted. + /// Execution context. + /// Dictionary of using variable map. + internal static Dictionary GetUsingValuesForEachParallel( + ScriptBlock scriptBlock, + bool isTrustedInput, + ExecutionContext context) + { + // Using variables for Foreach-Object -Parallel use are restricted to be within the + // Foreach-Object -Parallel call scope. This will filter the using variable map to variables + // only within the current (outer) Foreach-Object -Parallel call scope. + var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(scriptBlock.Ast).ToList(); + UsingExpressionAst usingAst = null; + var usingValueMap = new Dictionary(usingAsts.Count); + Version oldStrictVersion = null; + try + { + if (context != null) + { + oldStrictVersion = context.EngineSessionState.CurrentScope.StrictModeVersion; + context.EngineSessionState.CurrentScope.StrictModeVersion = PSVersionInfo.PSVersion; + } + + for (int i = 0; i < usingAsts.Count; ++i) + { + usingAst = (UsingExpressionAst)usingAsts[i]; + if (IsInForeachParallelCallingScope(scriptBlock.Ast, usingAst)) + { + var value = Compiler.GetExpressionValue(usingAst.SubExpression, isTrustedInput, context); + string usingAstKey = PsUtils.GetUsingExpressionKey(usingAst); + usingValueMap.TryAdd(usingAstKey, value); + } + } + } + catch (RuntimeException rte) + { + if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal)) + { + throw InterpreterError.NewInterpreterException( + targetObject: null, + exceptionType: typeof(RuntimeException), + errorPosition: usingAst.Extent, + resourceIdAndErrorId: "UsingVariableIsUndefined", + resourceString: AutomationExceptions.UsingVariableIsUndefined, + args: rte.ErrorRecord.TargetObject); + } + } + finally + { + if (context != null) + { + context.EngineSessionState.CurrentScope.StrictModeVersion = oldStrictVersion; + } + } + + 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. + /// True if using expression is in current call scope. + private static bool IsInForeachParallelCallingScope( + Ast scriptblockAst, + UsingExpressionAst usingAst) + { + Diagnostics.Assert(usingAst != null, "usingAst argument cannot be null."); + + /* + Example: + $Test1 = "Hello" + 1 | ForEach-Object -Parallel { + $using:Test1 + $Test2 = "Goodbye" + 1 | ForEach-Object -Parallel { + $using:Test1 # Invalid using scope + $using:Test2 # Valid using scope + } + } + */ + + // Search up the parent Ast chain for 'Foreach-Object -Parallel' commands. + Ast currentParent = usingAst.Parent; + while (currentParent != scriptblockAst) + { + // Look for Foreach-Object outer commands + if (currentParent is CommandAst commandAst && + FindForEachInCommand(commandAst)) + { + // Using Ast is outside the invoking foreach scope. + return false; + } + + currentParent = currentParent.Parent; + } + + return true; + } + /// /// Get using values in the dictionary form. /// @@ -342,11 +493,16 @@ internal static object[] GetUsingValuesAsArray(ScriptBlock scriptBlock, bool isT /// A tuple of the dictionary-form and the array-form using values. /// If the array-form using value is null, then there are UsingExpressions used in different scopes. /// - private static Tuple, object[]> GetUsingValues(Ast body, bool isTrustedInput, ExecutionContext context, Dictionary variables, bool filterNonUsingVariables) + private static Tuple, object[]> GetUsingValues( + Ast body, + bool isTrustedInput, + ExecutionContext context, + Dictionary variables, + bool filterNonUsingVariables) { Diagnostics.Assert(context != null || variables != null, "can't retrieve variables with no context and no variables"); - var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressionExceptForWorkflow(body).ToList(); + var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(body).ToList(); var usingValueArray = new object[usingAsts.Count]; var usingValueMap = new Dictionary(usingAsts.Count); HashSet usingVarNames = (variables != null && filterNonUsingVariables) ? new HashSet() : null; @@ -386,8 +542,7 @@ private static Tuple, object[]> GetUsingValues(Ast bo if (variables != null) { - var variableAst = usingAst.SubExpression as VariableExpressionAst; - if (variableAst == null) + if (usingAst.SubExpression is not VariableExpressionAst variableAst) { throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), usingAst.Extent, "CantGetUsingExpressionValueWithSpecifiedVariableDictionary", AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary, usingAst.Extent.Text); @@ -456,7 +611,7 @@ private static Tuple, object[]> GetUsingValues(Ast bo /// Check if the given UsingExpression is in a different scope from the previous UsingExpression that we analyzed. /// /// - /// Note that the value of is retrieved by calling 'UsingExpressionAstSearcher.FindAllUsingExpressionExceptForWorkflow'. + /// Note that the value of is retrieved by calling 'UsingExpressionAstSearcher.FindAllUsingExpressions'. /// So is guaranteed not inside a workflow. /// /// The UsingExpression to analyze. @@ -547,7 +702,6 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) Diagnostics.Assert(commandAst.Redirections.Count == 1, "only 1 kind of redirection is supported"); Diagnostics.Assert(commandAst.Redirections[0] is MergingRedirectionAst, "unexpected redirection type"); - PipelineResultTypes toType = PipelineResultTypes.Output; PipelineResultTypes fromType; switch (commandAst.Redirections[0].FromStream) { @@ -581,7 +735,7 @@ private void ConvertCommand(CommandAst commandAst, bool isTrustedInput) break; } - command.MergeMyResults(fromType, toType); + command.MergeMyResults(fromType, toResult: PipelineResultTypes.Output); } _powershell.AddCommand(command); @@ -748,7 +902,7 @@ private void GetSplattedVariable(VariableExpressionAst variableAst) foreach (var splattedParameter in PipelineOps.Splat(splattedValue, variableAst)) { CommandParameter publicParameter = CommandParameter.FromCommandParameterInternal(splattedParameter); - _powershell.AddParameter(publicParameter.Name, publicParameter.Value); + _powershell.AddParameter(publicParameter); } } @@ -792,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/scriptparameterbinder.cs b/src/System.Management.Automation/engine/scriptparameterbinder.cs index c9944678ad8..d5d1dfbc61c 100644 --- a/src/System.Management.Automation/engine/scriptparameterbinder.cs +++ b/src/System.Management.Automation/engine/scriptparameterbinder.cs @@ -161,7 +161,7 @@ internal object GetDefaultScriptParameterValue(RuntimeDefinedParameter parameter /// /// The script that is being bound to. /// - internal ScriptBlock Script { get; private set; } + internal ScriptBlock Script { get; } internal SessionStateScope LocalScope { get; set; } diff --git a/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs b/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs index 9a63b1969a8..5d946b4d60a 100644 --- a/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs +++ b/src/System.Management.Automation/engine/scriptparameterbindercontroller.cs @@ -63,7 +63,7 @@ internal ScriptParameterBinderController( /// /// Holds the set of parameters that were not bound to any argument (i.e $args) /// - internal List DollarArgs { get; private set; } + internal List DollarArgs { get; } /// /// Binds the command line parameters for shell functions/filters/scripts/scriptblocks. @@ -77,16 +77,10 @@ internal ScriptParameterBinderController( internal void BindCommandLineParameters(Collection arguments) { // Add the passed in arguments to the unboundArguments collection - - foreach (CommandParameterInternal argument in arguments) - { - UnboundArguments.Add(argument); - } - + InitUnboundArguments(arguments); ReparseUnboundArguments(); - // To support named parameters you just have un-comment the following line - UnboundArguments = BindParameters(UnboundArguments); + UnboundArguments = BindNamedParameters(uint.MaxValue, UnboundArguments); ParameterBindingException parameterBindingError; UnboundArguments = @@ -136,75 +130,6 @@ internal override bool BindParameter(CommandParameterInternal argument, Paramete return true; } - /// - /// Binds the specified parameters to the shell function. - /// - /// - /// The arguments to bind. - /// - internal override Collection BindParameters(Collection arguments) - { - Collection result = new Collection(); - - foreach (CommandParameterInternal argument in arguments) - { - if (!argument.ParameterNameSpecified) - { - result.Add(argument); - continue; - } - - // We don't want to throw an exception yet because - // the parameter might be a positional argument - - MergedCompiledCommandParameter parameter = - BindableParameters.GetMatchingParameter( - argument.ParameterName, - false, true, - new InvocationInfo(this.InvocationInfo.MyCommand, argument.ParameterExtent)); - - // If the parameter is not in the specified parameter set, - // throw a binding exception - - if (parameter != null) - { - // Now check to make sure it hasn't already been - // bound by looking in the boundParameters collection - - if (BoundParameters.ContainsKey(parameter.Parameter.Name)) - { - ParameterBindingException bindingException = - new ParameterBindingException( - ErrorCategory.InvalidArgument, - this.InvocationInfo, - GetParameterErrorExtent(argument), - argument.ParameterName, - null, - null, - ParameterBinderStrings.ParameterAlreadyBound, - nameof(ParameterBinderStrings.ParameterAlreadyBound)); - - throw bindingException; - } - - BindParameter(uint.MaxValue, argument, parameter, ParameterBindingFlags.ShouldCoerceType); - } - else if (argument.ParameterName.Equals(Language.Parser.VERBATIM_PARAMETERNAME, StringComparison.Ordinal)) - { - // We sometimes send a magic parameter from a remote machine with the values referenced via - // a using expression ($using:x). We then access these values via PSBoundParameters, so - // "bind" them here. - DefaultParameterBinder.CommandLineParameters.SetImplicitUsingParameters(argument.ArgumentValue); - } - else - { - result.Add(argument); - } - } - - return result; - } - /// /// Takes the remaining arguments that haven't been bound, and binds /// them to $args. diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index b70c7037c26..add0eab25dc 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. /// @@ -174,7 +212,7 @@ public static object[] DeserializeAsList(string source) /// /// Default depth of serialization. /// - private static int s_mshDefaultSerializationDepth = 1; + private static readonly int s_mshDefaultSerializationDepth = 1; } /// @@ -201,7 +239,7 @@ internal Serializer(XmlWriter writer) /// Writer to be used for serialization. /// Depth of serialization. /// - /// if true then types.ps1xml can override depth + /// if then types.ps1xml can override depth /// for a particular types (using SerializationDepth property) /// internal Serializer(XmlWriter writer, int depth, bool useDepthFromTypes) @@ -322,7 +360,7 @@ internal DeserializationContext(DeserializationOptions options, PSRemotingCrypto /// is used by PriorityReceivedDataCollection (remoting) to process incoming data from the /// remote end. A value of Null means that the max memory is unlimited. /// - internal int? MaximumAllowedMemory { set; get; } + internal int? MaximumAllowedMemory { get; set; } /// /// Logs that memory used by deserialized objects is not related to the size of input xml. @@ -346,7 +384,7 @@ internal void LogExtraMemoryUsage(int amountOfExtraMemory) throw new XmlException(message); } - _totalDataProcessedSoFar = _totalDataProcessedSoFar + amountOfExtraMemory; + _totalDataProcessedSoFar += amountOfExtraMemory; } } @@ -540,7 +578,7 @@ private void Start() // If version is not provided, we assume it is the default string version = InternalSerializer.DefaultVersion; - if (DeserializationOptions.NoRootElement == (_context.options & DeserializationOptions.NoRootElement)) + if ((_context.options & DeserializationOptions.NoRootElement) == DeserializationOptions.NoRootElement) { _done = _reader.EOF; } @@ -569,9 +607,9 @@ private void Start() internal bool Done() { - if (_done == false) + if (!_done) { - if (DeserializationOptions.NoRootElement == (_context.options & DeserializationOptions.NoRootElement)) + if ((_context.options & DeserializationOptions.NoRootElement) == DeserializationOptions.NoRootElement) { _done = _reader.EOF; } @@ -661,7 +699,7 @@ internal static void AddDeserializationPrefix(ref string type) /// /// /// - /// true if is either a live or deserialized instance of class or one of its subclasses; false otherwise. + /// if is either a live or deserialized instance of class or one of its subclasses; otherwise. internal static bool IsInstanceOfType(object o, Type type) { if (type == null) @@ -682,7 +720,7 @@ internal static bool IsInstanceOfType(object o, Type type) /// /// /// - /// true if is a deserialized instance of class or one of its subclasses; false otherwise. + /// if is a deserialized instance of class or one of its subclasses; otherwise. internal static bool IsDeserializedInstanceOfType(object o, Type type) { if (type == null) @@ -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. /// /// @@ -791,7 +829,7 @@ internal enum ContainerType List, Enumerable, None - }; + } /// /// This internal helper class provides methods for serializing mshObject. @@ -866,7 +904,7 @@ internal TypeTable TypeTable /// internal void Start() { - if (SerializationOptions.NoRootElement != (_context.options & SerializationOptions.NoRootElement)) + if ((_context.options & SerializationOptions.NoRootElement) != SerializationOptions.NoRootElement) { this.WriteStartElement(SerializationStrings.RootElementTag); this.WriteAttribute(SerializationStrings.VersionAttribute, InternalSerializer.DefaultVersion); @@ -878,7 +916,7 @@ internal void Start() /// internal void End() { - if (SerializationOptions.NoRootElement != (_context.options & SerializationOptions.NoRootElement)) + if ((_context.options & SerializationOptions.NoRootElement) != SerializationOptions.NoRootElement) { _writer.WriteEndElement(); } @@ -1543,7 +1581,7 @@ int depth _writer.WriteEndElement(); } - private static Lazy s_cimSerializer = new Lazy(CimSerializer.Create); + private static readonly Lazy s_cimSerializer = new Lazy(CimSerializer.Create); private void PrepareCimInstanceForSerialization(PSObject psObject, CimInstance cimInstance) { @@ -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))); } } @@ -1893,9 +1931,8 @@ private Collection> ExtendedMembersCollection { get { - return _extendedMembersCollection ?? - (_extendedMembersCollection = - PSObject.GetMemberCollection(PSMemberViewTypes.Extended, _typeTable)); + return _extendedMembersCollection ??= + PSObject.GetMemberCollection(PSMemberViewTypes.Extended, _typeTable); } } @@ -1905,8 +1942,7 @@ private Collection> AllPropertiesCollection { get { - return _allPropertiesCollection ?? - (_allPropertiesCollection = PSObject.GetPropertyCollection(PSMemberViewTypes.All, _typeTable)); + return _allPropertiesCollection ??= PSObject.GetPropertyCollection(PSMemberViewTypes.All, _typeTable); } } @@ -1982,8 +2018,7 @@ int depth foreach (PSMemberInfo info in propertyCollection) { - PSProperty prop = info as PSProperty; - if (prop == null) + if (info is not PSProperty prop) { continue; } @@ -2163,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); @@ -2311,7 +2349,7 @@ private int GetDepthOfSerialization(object source, int depth) return 1; } - if (0 != (_context.options & SerializationOptions.UseDepthFromTypes)) + if ((_context.options & SerializationOptions.UseDepthFromTypes) != 0) { // get the depth from the PSObject // NOTE: we assume that the depth out of the PSObject is > 0 @@ -2331,7 +2369,7 @@ private int GetDepthOfSerialization(object source, int depth) } } - if (0 != (_context.options & SerializationOptions.PreserveSerializationSettingOfOriginal)) + if ((_context.options & SerializationOptions.PreserveSerializationSettingOfOriginal) != 0) { if ((pso.IsDeserialized) && (depth <= 0)) { @@ -2751,7 +2789,7 @@ internal static void WriteSecureString(InternalSerializer serializer, string str private void WriteStartElement(string elementTag) { Dbg.Assert(!string.IsNullOrEmpty(elementTag), "Caller should validate the parameter"); - if (SerializationOptions.NoNamespace == (_context.options & SerializationOptions.NoNamespace)) + if ((_context.options & SerializationOptions.NoNamespace) == SerializationOptions.NoNamespace) { _writer.WriteStartElement(elementTag); } @@ -2829,7 +2867,7 @@ internal static string EncodeString(string s) /// /// This is the real workhorse that encodes strings. - /// See for more information. + /// See for more information. /// /// String to encode. /// IndexOfFirstEncodableCharacter. @@ -2909,7 +2947,7 @@ private void WriteEncodedElementString(string name, string value) value = EncodeString(value); - if (SerializationOptions.NoNamespace == (_context.options & SerializationOptions.NoNamespace)) + if ((_context.options & SerializationOptions.NoNamespace) == SerializationOptions.NoNamespace) { _writer.WriteElementString(name, value); } @@ -2958,6 +2996,10 @@ private bool UnknownTagsAllowed } } + [SuppressMessage( + "Performance", + "CA1822:Mark members as static", + Justification = "Accesses instance members in preprocessor branch.")] private bool DuplicateRefIdsAllowed { get @@ -3006,7 +3048,7 @@ internal InternalDeserializer(XmlReader reader, DeserializationContext context) #region Known CIMTypes - private static Lazy> s_knownCimArrayTypes = new Lazy>( + private static readonly Lazy> s_knownCimArrayTypes = new Lazy>( () => new HashSet { @@ -3197,7 +3239,7 @@ private void CheckIfStopping() internal const string CimHashCodeProperty = "Hash"; internal const string CimMiXmlProperty = "MiXml"; - private bool RehydrateCimInstanceProperty( + private static bool RehydrateCimInstanceProperty( CimInstance cimInstance, PSPropertyInfo deserializedProperty, HashSet namesOfModifiedProperties) @@ -3298,7 +3340,7 @@ private bool RehydrateCimInstanceProperty( return true; } - private static Lazy s_cimDeserializer = new Lazy(CimDeserializer.Create); + private static readonly Lazy s_cimDeserializer = new Lazy(CimDeserializer.Create); private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) { @@ -3331,32 +3373,28 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) PSObject psoDeserializedClass = PSObject.AsPSObject(deserializedClass); - PSPropertyInfo namespaceProperty = psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] as PSPropertyInfo; - if (namespaceProperty == null) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] is not PSPropertyInfo namespaceProperty) { return null; } string cimNamespace = namespaceProperty.Value as string; - PSPropertyInfo classNameProperty = psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] as PSPropertyInfo; - if (classNameProperty == null) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] is not PSPropertyInfo classNameProperty) { return null; } string cimClassName = classNameProperty.Value as string; - PSPropertyInfo computerNameProperty = psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] as PSPropertyInfo; - if (computerNameProperty == null) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] is not PSPropertyInfo computerNameProperty) { return null; } string computerName = computerNameProperty.Value as string; - PSPropertyInfo hashCodeProperty = psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] as PSPropertyInfo; - if (hashCodeProperty == null) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] is not PSPropertyInfo hashCodeProperty) { return null; } @@ -3372,7 +3410,7 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) hashCodeObject = ((PSObject)hashCodeObject).BaseObject; } - if (!(hashCodeObject is int)) + if (hashCodeObject is not int) { return null; } @@ -3426,7 +3464,7 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) private PSObject RehydrateCimInstance(PSObject deserializedObject) { - if (!(deserializedObject.BaseObject is PSCustomObject)) + if (deserializedObject.BaseObject is not PSCustomObject) { return deserializedObject; } @@ -3461,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); } @@ -3473,8 +3511,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) { foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.AdaptedMembers) { - PSPropertyInfo deserializedProperty = deserializedMemberInfo as PSPropertyInfo; - if (deserializedProperty == null) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -3494,8 +3531,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) // process properties that were originally "extended" properties foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.InstanceMembers) { - PSPropertyInfo deserializedProperty = deserializedMemberInfo as PSPropertyInfo; - if (deserializedProperty == null) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -3622,7 +3658,7 @@ private PSObject ReadPSObject() PSObject dso = ReadAttributeAndCreatePSObject(); // Read start element tag - if (ReadStartElementAndHandleEmpty(SerializationStrings.PSObjectTag) == false) + if (!ReadStartElementAndHandleEmpty(SerializationStrings.PSObjectTag)) { // Empty element. return dso; @@ -3675,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)) { @@ -3939,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: @@ -3993,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) @@ -4044,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) { @@ -4088,7 +4158,7 @@ private object ReadDictionary(ContainerType ct) ReadEndElement(); } - return table; + return dictionary.DictionaryObject; } #endregion known containers @@ -4425,7 +4495,7 @@ internal static object DeserializeScriptBlock(InternalDeserializer deserializer) { Dbg.Assert(deserializer != null, "Caller should validate the parameter"); string scriptBlockBody = deserializer.ReadDecodedElementString(SerializationStrings.ScriptBlockTag); - if (DeserializationOptions.DeserializeScriptBlocks == (deserializer._context.options & DeserializationOptions.DeserializeScriptBlocks)) + if ((deserializer._context.options & DeserializationOptions.DeserializeScriptBlocks) == DeserializationOptions.DeserializeScriptBlocks) { return ScriptBlock.Create(scriptBlockBody); } @@ -4659,7 +4729,7 @@ internal static object DeserializeProgressRecord(InternalDeserializer deserializ activityId = int.Parse(deserializer.ReadDecodedElementString(SerializationStrings.ProgressRecordActivityId), CultureInfo.InvariantCulture); object tmp = deserializer.ReadOneObject(); - currentOperation = (tmp == null) ? null : tmp.ToString(); + currentOperation = tmp?.ToString(); parentActivityId = int.Parse(deserializer.ReadDecodedElementString(SerializationStrings.ProgressRecordParentActivityId), CultureInfo.InvariantCulture); percentComplete = int.Parse(deserializer.ReadDecodedElementString(SerializationStrings.ProgressRecordPercentComplete), CultureInfo.InvariantCulture); @@ -4742,7 +4812,7 @@ private bool IsNextElement(string tag) { Dbg.Assert(!string.IsNullOrEmpty(tag), "Caller should validate the parameter"); return (_reader.LocalName == tag) && - ((0 != (_context.options & DeserializationOptions.NoNamespace)) || + (((_context.options & DeserializationOptions.NoNamespace) != 0) || (_reader.NamespaceURI == SerializationStrings.MonadNamespace)); } @@ -4762,7 +4832,7 @@ internal bool ReadStartElementAndHandleEmpty(string element) // This takes care of the case: or . In // this case isEmpty is false. - if (isEmpty == false && _reader.NodeType == XmlNodeType.EndElement) + if (!isEmpty && _reader.NodeType == XmlNodeType.EndElement) { ReadEndElement(); isEmpty = true; @@ -4775,7 +4845,7 @@ private void ReadStartElement(string element) { Dbg.Assert(!string.IsNullOrEmpty(element), "Caller should validate the parameter"); - if (DeserializationOptions.NoNamespace == (_context.options & DeserializationOptions.NoNamespace)) + if ((_context.options & DeserializationOptions.NoNamespace) == DeserializationOptions.NoNamespace) { _reader.ReadStartElement(element); } @@ -4799,7 +4869,7 @@ private string ReadDecodedElementString(string element) this.CheckIfStopping(); string temp = null; - if (DeserializationOptions.NoNamespace == (_context.options & DeserializationOptions.NoNamespace)) + if ((_context.options & DeserializationOptions.NoNamespace) == DeserializationOptions.NoNamespace) { temp = _reader.ReadElementContentAsString(element, string.Empty); } @@ -4921,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"); } @@ -4976,7 +5046,7 @@ internal string SetRefId(T t) } /// - /// Gets a RefId already assigned for the given object or null if there is no associated ref id. + /// Gets a RefId already assigned for the given object or if there is no associated ref id. /// /// /// @@ -5091,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 @@ -5644,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) { @@ -5879,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 @@ -5905,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) { @@ -5937,7 +6003,7 @@ private PSPrimitiveDictionary(System.Runtime.Serialization.SerializationInfo inf #region Plumbing to make Hashtable reject all non-primitive types - private string VerifyKey(object key) + private static string VerifyKey(object key) { key = PSObject.Base(key); string keyAsString = key as string; @@ -5980,7 +6046,7 @@ private 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) @@ -6037,8 +6103,8 @@ private void VerifyValue(object value) /// public override void Add(object key, object value) { - string keyAsString = this.VerifyKey(key); - this.VerifyValue(value); + string keyAsString = VerifyKey(key); + VerifyValue(value); base.Add(keyAsString, value); } @@ -6048,7 +6114,7 @@ public override void Add(object key, object value) /// The key whose value to get or set. /// The value associated with the specified key. /// - /// If the specified key is not found, attempting to get it returns null + /// If the specified key is not found, attempting to get it returns /// and attempting to set it creates a new element using the specified key. /// /// @@ -6065,8 +6131,8 @@ public override object this[object key] set { - string keyAsString = this.VerifyKey(key); - this.VerifyValue(value); + string keyAsString = VerifyKey(key); + VerifyValue(value); base[keyAsString] = value; } } @@ -6077,7 +6143,7 @@ public override object this[object key] /// The key whose value to get or set. /// The value associated with the specified key. /// - /// If the specified key is not found, attempting to get it returns null + /// If the specified key is not found, attempting to get it returns /// and attempting to set it creates a new element using the specified key. /// /// @@ -6094,7 +6160,7 @@ public object this[string key] set { - this.VerifyValue(value); + VerifyValue(value); base[key] = value; } } @@ -6498,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. /// /// @@ -6539,7 +6605,7 @@ internal static PSPrimitiveDictionary CloneAndAddPSVersionTable(PSPrimitiveDicti /// The root dictionary. /// /// A chain of keys leading from the root dictionary () to the value. - /// true if the value was found and was of the correct type; false otherwise. + /// if the value was found and was of the correct type; otherwise. internal static bool TryPathGet(IDictionary data, out T result, params string[] keys) { Dbg.Assert(keys != null, "Caller should verify that keys != null"); @@ -6600,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 { @@ -6857,14 +6922,14 @@ internal static T GetPropertyValue(PSObject pso, string propertyName, Rehydra Dbg.Assert(!string.IsNullOrEmpty(propertyName), "Caller should verify propertyName != null"); PSPropertyInfo property = pso.Properties[propertyName]; - if ((property == null) && (RehydrationFlags.MissingPropertyOk == (flags & RehydrationFlags.MissingPropertyOk))) + if ((property == null) && ((flags & RehydrationFlags.MissingPropertyOk) == RehydrationFlags.MissingPropertyOk)) { return default(T); } else { object propertyValue = property.Value; - if ((propertyValue == null) && (RehydrationFlags.NullValueOk == (flags & RehydrationFlags.NullValueOk))) + if ((propertyValue == null) && ((flags & RehydrationFlags.NullValueOk) == RehydrationFlags.NullValueOk)) { return default(T); } @@ -6876,27 +6941,27 @@ internal static T GetPropertyValue(PSObject pso, string propertyName, Rehydra } } - private static ListType RehydrateList(PSObject pso, string propertyName, RehydrationFlags flags) - where ListType : IList, new() + private static TList RehydrateList(PSObject pso, string propertyName, RehydrationFlags flags) + where TList : IList, new() { ArrayList deserializedList = GetPropertyValue(pso, propertyName, flags); if (deserializedList == null) { - if (RehydrationFlags.NullValueMeansEmptyList == (flags & RehydrationFlags.NullValueMeansEmptyList)) + if ((flags & RehydrationFlags.NullValueMeansEmptyList) == RehydrationFlags.NullValueMeansEmptyList) { - return new ListType(); + return new TList(); } else { - return default(ListType); + return default(TList); } } else { - ListType newList = new ListType(); + TList newList = new TList(); foreach (object deserializedItem in deserializedList) { - ItemType item = (ItemType)LanguagePrimitives.ConvertTo(deserializedItem, typeof(ItemType), CultureInfo.InvariantCulture); + TItem item = (TItem)LanguagePrimitives.ConvertTo(deserializedItem, typeof(TItem), CultureInfo.InvariantCulture); newList.Add(item); } @@ -7219,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; @@ -7228,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) @@ -7279,8 +7345,7 @@ public static UInt32 GetParameterSetMetadataFlags(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - ParameterSetMetadata parameterSetMetadata = instance.BaseObject as ParameterSetMetadata; - if (parameterSetMetadata == null) + if (instance.BaseObject is not ParameterSetMetadata parameterSetMetadata) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7301,8 +7366,7 @@ public static PSObject GetInvocationInfo(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - DebuggerStopEventArgs dbgStopEventArgs = instance.BaseObject as DebuggerStopEventArgs; - if (dbgStopEventArgs == null) + if (instance.BaseObject is not DebuggerStopEventArgs dbgStopEventArgs) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7561,8 +7625,7 @@ public static Guid GetFormatViewDefinitionInstanceId(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - FormatViewDefinition formatViewDefinition = instance.BaseObject as FormatViewDefinition; - if (formatViewDefinition == null) + if (instance.BaseObject is not FormatViewDefinition formatViewDefinition) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7614,4 +7677,3 @@ private static ExtendedTypeDefinition RehydrateExtendedTypeDefinition(PSObject d #endregion } } - diff --git a/src/System.Management.Automation/help/AliasHelpInfo.cs b/src/System.Management.Automation/help/AliasHelpInfo.cs index b51d1b192dc..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"); } @@ -70,7 +69,7 @@ internal override HelpCategory HelpCategory } } - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; /// /// Returns full help object for alias help. diff --git a/src/System.Management.Automation/help/AliasHelpProvider.cs b/src/System.Management.Automation/help/AliasHelpProvider.cs index 670c0202bc7..d93aa76b2f3 100644 --- a/src/System.Management.Automation/help/AliasHelpProvider.cs +++ b/src/System.Management.Automation/help/AliasHelpProvider.cs @@ -40,7 +40,7 @@ internal AliasHelpProvider(HelpSystem helpSystem) : base(helpSystem) /// of wildcard search patterns. This is currently not achievable /// through _commandDiscovery. /// - private SessionState _sessionState; + private readonly SessionState _sessionState; /// /// Command Discovery object for current session. @@ -50,7 +50,7 @@ internal AliasHelpProvider(HelpSystem helpSystem) : base(helpSystem) /// The AliasInfo object returned from _commandDiscovery is essential /// in creating AliasHelpInfo. /// - private CommandDiscovery _commandDiscovery; + private readonly CommandDiscovery _commandDiscovery; #region Common Properties @@ -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)) @@ -261,7 +261,7 @@ private static bool Match(HelpInfo helpInfo, HelpRequest helpRequest) if (helpRequest == null) return true; - if (0 == (helpRequest.HelpCategory & helpInfo.HelpCategory)) + if ((helpRequest.HelpCategory & helpInfo.HelpCategory) == 0) { return false; } 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/CabinetAPI.cs b/src/System.Management.Automation/help/CabinetAPI.cs index eb407f2ec62..9ccdc08baa6 100644 --- a/src/System.Management.Automation/help/CabinetAPI.cs +++ b/src/System.Management.Automation/help/CabinetAPI.cs @@ -78,9 +78,9 @@ internal abstract class ICabinetExtractorLoader /// /// Used to create a CabinetExtractor class. /// - internal class CabinetExtractorFactory + internal static class CabinetExtractorFactory { - private static ICabinetExtractorLoader s_cabinetLoader; + private static readonly ICabinetExtractorLoader s_cabinetLoader; internal static readonly ICabinetExtractor EmptyExtractor = new EmptyCabinetExtractor(); /// @@ -137,4 +137,3 @@ protected override void Dispose(bool disposing) } } } - diff --git a/src/System.Management.Automation/help/CabinetNativeApi.cs b/src/System.Management.Automation/help/CabinetNativeApi.cs index 4b19861e473..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(); @@ -84,7 +81,7 @@ protected override void Dispose(bool disposing) } /// - /// Finalizer to ensure destruction of unmanaged resources. + /// Finalizes an instance of the class. /// ~CabinetExtractor() { @@ -152,7 +149,7 @@ private void populateDelegates() private void CleanUpDelegates() { // Free GCHandles so that the memory they point to may be unpinned (garbage collected) - if (_fdiAllocHandle != null) + if (_fdiAllocHandle.IsAllocated) { _fdiAllocHandle.Free(); _fdiFreeHandle.Free(); @@ -164,7 +161,7 @@ private void CleanUpDelegates() _fdiNotifyHandle.Free(); } } - }; + } // CabinetExtractor loader implementation internal class CabinetExtractorLoader : ICabinetExtractorLoader @@ -175,7 +172,7 @@ internal class CabinetExtractorLoader : ICabinetExtractorLoader internal static CabinetExtractorLoader GetInstance() { - if (0 == System.Threading.Interlocked.CompareExchange(ref s_created, 1, 0)) + if (System.Threading.Interlocked.CompareExchange(ref s_created, 1, 0) == 0) { s_instance = new CabinetExtractorLoader(); s_extractorInstance = new CabinetExtractor(); @@ -188,7 +185,7 @@ internal override ICabinetExtractor GetCabinetExtractor() { return s_extractorInstance; } - }; + } internal static class CabinetNativeApi { @@ -458,27 +455,27 @@ internal static FileMode ConvertOpflagToFileMode(int oflag) { // Note: This is not done in a switch because the order of tests matters. - if ((int)(OpFlags.Create | OpFlags.Excl) == (oflag & (int)(OpFlags.Create | OpFlags.Excl))) + if ((oflag & (int)(OpFlags.Create | OpFlags.Excl)) == (int)(OpFlags.Create | OpFlags.Excl)) { return FileMode.CreateNew; } - else if ((int)(OpFlags.Create | OpFlags.Truncate) == (oflag & (int)(OpFlags.Create | OpFlags.Truncate))) + else if ((oflag & (int)(OpFlags.Create | OpFlags.Truncate)) == (int)(OpFlags.Create | OpFlags.Truncate)) { return FileMode.OpenOrCreate; } - else if (0 != (oflag & (int)OpFlags.Append)) + else if ((oflag & (int)OpFlags.Append) != 0) { return FileMode.Append; } - else if (0 != (oflag & (int)OpFlags.Create)) + else if ((oflag & (int)OpFlags.Create) != 0) { return FileMode.Create; } - else if (0 != (oflag & (int)OpFlags.RdWr)) + else if ((oflag & (int)OpFlags.RdWr) != 0) { return FileMode.Open; } - else if (0 != (oflag & (int)OpFlags.Truncate)) + else if ((oflag & (int)OpFlags.Truncate) != 0) { return FileMode.Truncate; } @@ -497,15 +494,15 @@ internal static FileAccess ConvertPermissionModeToFileAccess(int pmode) { // Note: This is not done in a switch because the order of tests matters. - if ((int)(PermissionMode.Read | PermissionMode.Write) == (pmode & (int)(PermissionMode.Read | PermissionMode.Write))) + if ((pmode & (int)(PermissionMode.Read | PermissionMode.Write)) == (int)(PermissionMode.Read | PermissionMode.Write)) { return FileAccess.ReadWrite; } - else if (0 != (pmode & (int)PermissionMode.Read)) + else if ((pmode & (int)PermissionMode.Read) != 0) { return FileAccess.Read; } - else if (0 != (pmode & (int)PermissionMode.Write)) + else if ((pmode & (int)PermissionMode.Write) != 0) { return FileAccess.Write; } @@ -524,15 +521,15 @@ internal static FileShare ConvertPermissionModeToFileShare(int pmode) { // Note: This is not done in a switch because the order of tests matters. - if ((int)(PermissionMode.Read | PermissionMode.Write) == (pmode & (int)(PermissionMode.Read | PermissionMode.Write))) + if ((pmode & (int)(PermissionMode.Read | PermissionMode.Write)) == (int)(PermissionMode.Read | PermissionMode.Write)) { return FileShare.ReadWrite; } - else if (0 != (pmode & (int)PermissionMode.Read)) + else if ((pmode & (int)PermissionMode.Read) != 0) { return FileShare.Read; } - else if (0 != (pmode & (int)PermissionMode.Write)) + else if ((pmode & (int)PermissionMode.Write) != 0) { return FileShare.Write; } @@ -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, @@ -589,7 +586,7 @@ internal class FdiNotification internal short iCabinet; // USHORT internal short iFolder; // USHORT internal int fdie; // FDIERROR - }; + } internal enum FdiNotificationType : int { @@ -607,7 +604,7 @@ internal class FdiERF internal int erfOper; internal int erfType; internal bool fError; - }; + } internal sealed class FdiContextHandle : SafeHandleZeroOrMinusOneIsInvalid { diff --git a/src/System.Management.Automation/help/CommandHelpProvider.cs b/src/System.Management.Automation/help/CommandHelpProvider.cs index 8771c4eced7..15af80745db 100644 --- a/src/System.Management.Automation/help/CommandHelpProvider.cs +++ b/src/System.Management.Automation/help/CommandHelpProvider.cs @@ -48,7 +48,7 @@ static CommandHelpProvider() s_engineModuleHelpFileCache.Add("Microsoft.WSMan.Management", "Microsoft.Wsman.Management.dll-Help.xml"); } - private static Dictionary s_engineModuleHelpFileCache = new Dictionary(); + private static readonly Dictionary s_engineModuleHelpFileCache = new Dictionary(); private readonly ExecutionContext _context; @@ -84,7 +84,7 @@ internal override HelpCategory HelpCategory #region Help Provider Interface - private void GetModulePaths(CommandInfo commandInfo, out string moduleName, out string moduleDir, out string nestedModulePath) + private static void GetModulePaths(CommandInfo commandInfo, out string moduleName, out string moduleDir, out string nestedModulePath) { Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null"); @@ -132,7 +132,7 @@ private void GetModulePaths(CommandInfo commandInfo, out string moduleName, out } } - private string GetHelpName(CommandInfo commandInfo) + private static string GetHelpName(CommandInfo commandInfo) { Dbg.Assert(commandInfo != null, "Caller should verify that commandInfo != null"); @@ -765,9 +765,7 @@ private void ProcessUserDefinedHelpData(string mshSnapInId, UserDefinedHelpData if (helpInfo == null) return; - MamlCommandHelpInfo commandHelpInfo = helpInfo as MamlCommandHelpInfo; - - if (commandHelpInfo == null) + if (!(helpInfo is MamlCommandHelpInfo commandHelpInfo)) return; commandHelpInfo.AddUserDefinedData(userDefinedHelpData); @@ -881,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 @@ -952,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); @@ -1024,7 +1028,7 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool { if (decoratedSearch) { - if (target.IndexOf(StringLiterals.CommandVerbNounSeparator) >= 0) + if (target.Contains(StringLiterals.CommandVerbNounSeparator)) { patternList.Add(target + "*"); } @@ -1077,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; } @@ -1173,12 +1174,12 @@ private static bool Match(HelpInfo helpInfo, HelpRequest helpRequest, CommandInf if (helpRequest == null) return true; - if (0 == (helpRequest.HelpCategory & commandInfo.HelpCategory)) + if ((helpRequest.HelpCategory & commandInfo.HelpCategory) == 0) { return false; } - if (!(helpInfo is BaseCommandHelpInfo)) + if (helpInfo is not BaseCommandHelpInfo) return false; if (!Match(helpInfo.Component, helpRequest.Component)) @@ -1244,7 +1245,7 @@ private static bool Match(string target, ICollection patterns) } /// - /// Process helpInfo forwarded over from other other providers, specificly AliasHelpProvider. + /// Process helpInfo forwarded over from other providers, specificly AliasHelpProvider. /// This can return more than 1 helpinfo object. /// /// HelpInfo that is forwarded over. @@ -1252,7 +1253,7 @@ private static bool Match(string target, ICollection patterns) /// The result helpInfo objects after processing. internal override IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, HelpRequest helpRequest) { - HelpCategory categoriesHandled = (HelpCategory.Alias + const HelpCategory categoriesHandled = (HelpCategory.Alias | HelpCategory.ExternalScript | HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand); if ((helpInfo.HelpCategory & categoriesHandled) != 0) @@ -1277,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 } } @@ -1361,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 b8be27e99a0..bce76b005a2 100644 --- a/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs +++ b/src/System.Management.Automation/help/DefaultCommandHelpObjectBuilder.cs @@ -40,7 +40,7 @@ public int Compare(object x, object y) /// is present in the box. This class mimics the exact same structure as that of a MAML /// node, so that the default UX does not introduce regressions. /// - internal class DefaultCommandHelpObjectBuilder + internal static class DefaultCommandHelpObjectBuilder { internal static readonly string TypeNameForDefaultHelp = "ExtendedCmdletHelpInfo"; /// @@ -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 + /// Adds the globbing properties. + /// + /// HelpInfo object. + /// The attributes of the parameter (needed to look for PSTypeName). + private static void AddParameterGlobbingProperties(PSObject obj, IEnumerable attributes) + { + bool globbing = false; + + foreach (var attrib in attributes) + { + if (attrib is SupportsWildcardsAttribute) + { + globbing = true; + break; + } + } + + obj.Properties.Add(new PSNoteProperty("globbing", CultureInfo.CurrentCulture.TextInfo.ToLower(globbing.ToString()))); + } + /// /// Adds the parameterType properties. /// @@ -528,7 +551,7 @@ private static void AddParameterTypeProperties(PSObject obj, Type parameterType, PSObject mshObject = new PSObject(); mshObject.TypeNames.Clear(); - mshObject.TypeNames.Add(string.Format(CultureInfo.InvariantCulture, "{0}#type", DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp)); + mshObject.TypeNames.Add(string.Create(CultureInfo.InvariantCulture, $"{DefaultCommandHelpObjectBuilder.TypeNameForDefaultHelp}#type")); var parameterTypeString = CommandParameterSetInfo.GetParameterTypeString(parameterType, attributes); mshObject.Properties.Add(new PSNoteProperty("name", parameterTypeString)); @@ -582,9 +605,7 @@ internal static void AddInputTypesProperties(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 4d2a8100d0c..8ab4fa37ca6 100644 --- a/src/System.Management.Automation/help/HelpCategoryInvalidException.cs +++ b/src/System.Management.Automation/help/HelpCategoryInvalidException.cs @@ -16,14 +16,14 @@ 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 { /// /// Initializes a new instance of the HelpCategoryInvalidException class. /// /// The name of help category that is invalid. - public HelpCategoryInvalidException(string helpCategory) : base() + public HelpCategoryInvalidException(string helpCategory) + : base() { _helpCategory = helpCategory; CreateErrorRecord(); @@ -32,7 +32,8 @@ public HelpCategoryInvalidException(string helpCategory) : base() /// /// Initializes a new instance of the HelpCategoryInvalidException class. /// - public HelpCategoryInvalidException() : base() + public HelpCategoryInvalidException() + : base() { CreateErrorRecord(); } @@ -42,8 +43,10 @@ public HelpCategoryInvalidException() : base() /// /// The name of help category that is invalid. /// The inner exception of this exception. - public HelpCategoryInvalidException(string helpCategory, Exception innerException) : - base((innerException != null) ? innerException.Message : string.Empty, innerException) + public HelpCategoryInvalidException(string helpCategory, Exception innerException) + : base( + (innerException != null) ? innerException.Message : string.Empty, + innerException) { _helpCategory = helpCategory; CreateErrorRecord(); @@ -72,7 +75,7 @@ public ErrorRecord ErrorRecord } } - private string _helpCategory = System.Management.Automation.HelpCategory.None.ToString(); + private readonly string _helpCategory = System.Management.Automation.HelpCategory.None.ToString(); /// /// Gets name of the help category that is invalid. @@ -109,31 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 852db86c267..3bed2a26820 100644 --- a/src/System.Management.Automation/help/HelpCommands.cs +++ b/src/System.Management.Automation/help/HelpCommands.cs @@ -68,7 +68,7 @@ public GetHelpCommand() IgnoreCase = true)] public string[] Category { get; set; } - private string _provider = string.Empty; + private readonly string _provider = string.Empty; /// /// Changes the view of HelpObject returned. @@ -185,6 +185,11 @@ public SwitchParameter Examples [Parameter(ParameterSetName = "Online", Mandatory = true)] public SwitchParameter Online { + get + { + return _showOnlineHelp; + } + set { _showOnlineHelp = value; @@ -193,11 +198,6 @@ public SwitchParameter Online VerifyParameterForbiddenInRemoteRunspace(this, "Online"); } } - - get - { - return _showOnlineHelp; - } } private bool _showOnlineHelp; @@ -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 { @@ -262,7 +275,7 @@ protected override void ProcessRecord() this.graphicalHostReflectionWrapper = GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(this, "Microsoft.PowerShell.Commands.Internal.HelpWindowHelper"); } #endif - helpSystem.OnProgress += new HelpSystem.HelpProgressHandler(HelpSystem_OnProgress); + helpSystem.OnProgress += HelpSystem_OnProgress; bool failed = false; HelpCategory helpCategory = ToHelpCategory(Category, ref failed); @@ -302,7 +315,7 @@ protected override void ProcessRecord() return; } - if (0 == countOfHelpInfos) + if (countOfHelpInfos == 0) { firstHelpInfoObject = helpInfo; } @@ -321,14 +334,14 @@ 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 // Write full help as there is only one help info object - if (1 == countOfHelpInfos) + if (countOfHelpInfos == 1) { WriteObjectsOrShowOnlineHelp(firstHelpInfoObject, true); } @@ -354,7 +367,7 @@ protected override void ProcessRecord() } finally { - helpSystem.OnProgress -= new HelpSystem.HelpProgressHandler(HelpSystem_OnProgress); + helpSystem.OnProgress -= HelpSystem_OnProgress; HelpSystem_OnComplete(); // finally clear the ScriptBlockAst -> Token[] cache @@ -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) { @@ -516,7 +529,7 @@ private void ValidateAndThrowIfError(HelpCategory cat) } // categories that support -Parameter, -Role, -Functionality, -Component parameters - HelpCategory supportedCategories = + const HelpCategory supportedCategories = HelpCategory.Alias | HelpCategory.Cmdlet | HelpCategory.ExternalScript | HelpCategory.Filter | HelpCategory.Function | HelpCategory.ScriptCommand; @@ -683,7 +696,7 @@ private void LaunchOnlineHelp(Uri uriToLaunch) #endregion - private void HelpSystem_OnProgress(object sender, HelpProgressInfo arg) + private void HelpSystem_OnProgress(object sender, HelpProgressEventArgs arg) { var record = new ProgressRecord(0, this.CommandInfo.Name, arg.Activity) { @@ -721,10 +734,8 @@ internal static void VerifyParameterForbiddenInRemoteRunspace(Cmdlet cmdlet, str #endregion #region trace - - [TraceSourceAttribute("GetHelpCommand ", "GetHelpCommand ")] - private static PSTraceSource s_tracer = PSTraceSource.GetTracer("GetHelpCommand ", "GetHelpCommand "); - + [TraceSource("GetHelpCommand", "GetHelpCommand")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("GetHelpCommand", "GetHelpCommand"); #endregion } @@ -734,34 +745,38 @@ internal static void VerifyParameterForbiddenInRemoteRunspace(Cmdlet cmdlet, str public static class GetHelpCodeMethods { /// - /// Verifies if the InitialSessionState of the current process. + /// Checks whether the default runspace associated with the current thread has the standard Get-Help cmdlet. /// - /// + /// True if Get-Help is found, false otherwise. private static bool DoesCurrentRunspaceIncludeCoreHelpCmdlet() { - InitialSessionState iss = - System.Management.Automation.Runspaces.Runspace.DefaultRunspace.InitialSessionState; - if (iss != null) - { - IEnumerable publicGetHelpEntries = iss - .Commands["Get-Help"] - .Where(entry => entry.Visibility == SessionStateEntryVisibility.Public); - if (publicGetHelpEntries.Count() != 1) + InitialSessionState iss = Runspace.DefaultRunspace.InitialSessionState; + if (iss is null) + { + return false; + } + + Collection getHelpEntries = iss.Commands["Get-Help"]; + SessionStateCommandEntry getHelpEntry = null; + for (int i = 0; i < getHelpEntries.Count; ++i) + { + if (getHelpEntries[i].Visibility is not SessionStateEntryVisibility.Public) { - return false; + continue; } - foreach (SessionStateCommandEntry getHelpEntry in publicGetHelpEntries) + // If we have multiple entries for Get-Help, + // our assumption is that the standard Get-Help is not available. + if (getHelpEntry is not null) { - SessionStateCmdletEntry getHelpCmdlet = getHelpEntry as SessionStateCmdletEntry; - if ((getHelpCmdlet != null) && (getHelpCmdlet.ImplementingType.Equals(typeof(GetHelpCommand)))) - { - return true; - } + return false; } + + getHelpEntry = getHelpEntries[i]; } - return false; + return getHelpEntry is SessionStateCmdletEntry getHelpCmdlet + && getHelpCmdlet.ImplementingType == typeof(GetHelpCommand); } /// @@ -815,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()) @@ -835,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; } @@ -888,4 +902,3 @@ Uri result in } } } - diff --git a/src/System.Management.Automation/help/HelpCommentsParser.cs b/src/System.Management.Automation/help/HelpCommentsParser.cs index 77347527ab4..b4a21e04d69 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() { @@ -61,10 +61,10 @@ private HelpCommentsParser(CommandInfo commandInfo, List parameterDescri private readonly List _links = new List(); internal bool isExternalHelpSet = false; - private ScriptBlock _scriptBlock; - private CommandMetadata _commandMetadata; - private string _commandName; - private List _parameterDescriptions; + private readonly ScriptBlock _scriptBlock; + private readonly CommandMetadata _commandMetadata; + private readonly string _commandName; + private readonly List _parameterDescriptions; private XmlDocument _doc; internal static readonly string mshURI = "http://msh"; internal static readonly string mamlURI = "http://schemas.microsoft.com/maml/2004/10"; @@ -448,7 +448,7 @@ internal XmlDocument BuildXmlFromComments() foreach (string link in _links) { XmlElement navigationLink = _doc.CreateElement("maml:navigationLink", mamlURI); - bool isOnlineHelp = Uri.IsWellFormedUriString(Uri.EscapeUriString(link), UriKind.Absolute); + bool isOnlineHelp = Uri.IsWellFormedUriString(link, UriKind.Absolute); string nodeName = isOnlineHelp ? "maml:uri" : "maml:linkText"; XmlElement linkText = _doc.CreateElement(nodeName, mamlURI); XmlText linkText_text = _doc.CreateTextNode(link); @@ -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, @@ -496,7 +496,7 @@ private void BuildSyntaxForParameterSet(XmlElement command, XmlElement syntax, M private static void GetExampleSections(string content, out string prompt_str, out string code_str, out string remarks_str) { - string default_prompt_str = "PS > "; + const string default_prompt_str = "PS > "; var promptMatch = Regex.Match(content, "^.*?>"); prompt_str = promptMatch.Success ? promptMatch.Value : default_prompt_str; @@ -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'); } @@ -951,7 +951,7 @@ internal static bool IsCommentHelpText(List commentBlock) var result = new List(); // Any whitespace between the token and the first comment is allowed. - int nextMaxStartLine = Int32.MaxValue; + int nextMaxStartLine = int.MaxValue; for (int i = startIndex; i < tokens.Length; i++) { diff --git a/src/System.Management.Automation/help/HelpErrorTracer.cs b/src/System.Management.Automation/help/HelpErrorTracer.cs index 9c9fd0165b5..e5a9fea9e67 100644 --- a/src/System.Management.Automation/help/HelpErrorTracer.cs +++ b/src/System.Management.Automation/help/HelpErrorTracer.cs @@ -52,12 +52,12 @@ internal class HelpErrorTracer internal sealed class TraceFrame : IDisposable { // Following are help context information - private string _helpFile = string.Empty; + private readonly string _helpFile = string.Empty; // ErrorRecords accumulated during the help content loading. - private Collection _errors = new Collection(); + private readonly Collection _errors = new Collection(); - private HelpErrorTracer _helpTracer; + private readonly HelpErrorTracer _helpTracer; /// /// Constructor. Here help context information will be collected. /// diff --git a/src/System.Management.Automation/help/HelpFileHelpInfo.cs b/src/System.Management.Automation/help/HelpFileHelpInfo.cs index 1df1b70411b..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. @@ -50,8 +50,8 @@ private HelpFileHelpInfo(string name, string text, string filename) /// Name for the help info internal override string Name { get; } = string.Empty; - private string _filename = string.Empty; - private string _synopsis = string.Empty; + private readonly string _filename = string.Empty; + private readonly string _synopsis = string.Empty; /// /// Synopsis for the help info. /// diff --git a/src/System.Management.Automation/help/HelpFileHelpProvider.cs b/src/System.Management.Automation/help/HelpFileHelpProvider.cs index 2c527d2f95a..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)) @@ -268,16 +264,15 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool } } - private void GetModuleNameAndVersion(string psmodulePathRoot, string filePath, out string moduleName, out Version moduleVersion) + private static void GetModuleNameAndVersion(string psmodulePathRoot, string filePath, out string moduleName, out Version moduleVersion) { moduleVersion = null; moduleName = null; 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) { @@ -415,9 +410,8 @@ internal override void Reset() /// /// This will avoid one help file getting loaded again and again. /// - private Hashtable _helpFiles = new Hashtable(); + private readonly Hashtable _helpFiles = new Hashtable(); #endregion } } - diff --git a/src/System.Management.Automation/help/HelpNotFoundException.cs b/src/System.Management.Automation/help/HelpNotFoundException.cs index 355be44fd7c..109cd21dbd9 100644 --- a/src/System.Management.Automation/help/HelpNotFoundException.cs +++ b/src/System.Management.Automation/help/HelpNotFoundException.cs @@ -15,14 +15,14 @@ namespace Microsoft.PowerShell.Commands /// /// The exception that is thrown when there is no help found for a topic. /// - [Serializable] public class HelpNotFoundException : SystemException, IContainsErrorRecord { /// /// Initializes a new instance of the HelpNotFoundException class with the give help topic. /// /// The help topic for which help is not found. - public HelpNotFoundException(string helpTopic) : base() + public HelpNotFoundException(string helpTopic) + : base() { _helpTopic = helpTopic; CreateErrorRecord(); @@ -31,7 +31,8 @@ public HelpNotFoundException(string helpTopic) : base() /// /// Initializes a new instance of the HelpNotFoundException class. /// - public HelpNotFoundException() : base() + public HelpNotFoundException() + : base() { CreateErrorRecord(); } @@ -42,8 +43,10 @@ public HelpNotFoundException() : base() /// /// The help topic for which help is not found. /// The inner exception. - public HelpNotFoundException(string helpTopic, Exception innerException) : - base((innerException != null) ? innerException.Message : string.Empty, innerException) + public HelpNotFoundException(string helpTopic, Exception innerException) + : base( + (innerException != null) ? innerException.Message : string.Empty, + innerException) { _helpTopic = helpTopic; CreateErrorRecord(); @@ -77,7 +80,7 @@ public ErrorRecord ErrorRecord } } - private string _helpTopic = string.Empty; + private readonly string _helpTopic = string.Empty; /// /// Gets help topic for which help is not found. @@ -115,33 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 76ce3168432..3280a8d2540 100644 --- a/src/System.Management.Automation/help/HelpProvider.cs +++ b/src/System.Management.Automation/help/HelpProvider.cs @@ -50,7 +50,7 @@ internal HelpProvider(HelpSystem helpSystem) _helpSystem = helpSystem; } - private HelpSystem _helpSystem; + private readonly HelpSystem _helpSystem; internal HelpSystem HelpSystem { @@ -175,7 +175,7 @@ internal virtual IEnumerable ProcessForwardedHelp(HelpInfo helpInfo, H { // Win8: 508648. Remove the current provides category for resolving forward help as the current // help provider already process it. - helpInfo.ForwardHelpCategory = helpInfo.ForwardHelpCategory ^ this.HelpCategory; + helpInfo.ForwardHelpCategory ^= this.HelpCategory; yield return helpInfo; } @@ -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/HelpProviderWithCache.cs b/src/System.Management.Automation/help/HelpProviderWithCache.cs index 99dc3fa7a19..495ec6a0b5d 100644 --- a/src/System.Management.Automation/help/HelpProviderWithCache.cs +++ b/src/System.Management.Automation/help/HelpProviderWithCache.cs @@ -28,7 +28,7 @@ internal HelpProviderWithCache(HelpSystem helpSystem) : base(helpSystem) /// /// This hashtable is made case-insensitive so that helpInfo can be retrieved case insensitively. /// - private Hashtable _helpCache = new Hashtable(StringComparer.OrdinalIgnoreCase); + private readonly Hashtable _helpCache = new Hashtable(StringComparer.OrdinalIgnoreCase); /// /// Exact match help for a target. diff --git a/src/System.Management.Automation/help/HelpSystem.cs b/src/System.Management.Automation/help/HelpSystem.cs index fb92ed1c19c..8880e022cac 100644 --- a/src/System.Management.Automation/help/HelpSystem.cs +++ b/src/System.Management.Automation/help/HelpSystem.cs @@ -22,7 +22,7 @@ namespace System.Management.Automation /// /// Class HelpSystem implements the middle layer of Monad Help. /// - /// HelpSystem will provide functionalitys in following areas, + /// HelpSystem will provide functionalities in following areas, /// 1. Initialization and management of help providers /// 2. Help engine: this will invoke different providers based on user's request. /// 3. Help API: this is the API HelpSystem provide to get-help commandlet. @@ -31,7 +31,7 @@ namespace System.Management.Automation /// Initialization of different help providers needs some context information like "ExecutionContext" /// /// Help engine: - /// By default, HelpInfo will be retrieved in two phase: exact-match phase and search phase. + /// By default, HelpInfo will be retrieved in two phases: exact-match phase and search phase. /// /// Exact-match phase: help providers will be called in appropriate order to retrieve HelpInfo. /// If a match is found, help engine will stop and return the one and only HelpInfo retrieved. @@ -39,22 +39,22 @@ namespace System.Management.Automation /// Search phase: all relevant help providers will be called to retrieve HelpInfo. (Order doesn't /// matter in this case) Help engine will not stop until all help providers are called. /// - /// Behaviour of help engine can be modified based on Help API parameters in following ways, + /// Behavior of the help engine can be modified based on Help API parameters in the following ways: /// 1. limit the number of HelpInfo to be returned. /// 2. specify which providers will be used. /// 3. general help info returned in case the search target is empty. /// 4. default help info (or hint) returned in case no match is found. /// - /// Help Api: - /// Help Api is the function to be called by get-help commandlet. + /// Help API: + /// The Help API is the function to be called by the Get-Help cmdlet. /// - /// Following information needs to be provided in Help Api parameters, + /// The following information shall be provided in Help API parameters: /// 1. search target: (which can be one or multiple strings) - /// 2. help type: limit the type of help to be searched. + /// 2. help type: to limit the type of help to be searched /// 3. included fields: the fields to be included in the help info /// 4. excluded fields: the fields to be excluded in the help info - /// 5. max number of results to be returned: - /// 6. scoring algorithm for help results? + /// 5. max number of results to be returned + /// 6. scoring algorithm for help results /// 7. help reason: help can be directly invoked by end user or as a result of /// some command syntax error. /// @@ -64,8 +64,8 @@ namespace System.Management.Automation /// Help API's are internal. The only way to access help is by /// invoking the get-help command. /// - /// To support the scenario where multiple monad engine running in one process. It - /// is required that each monad engine has its one help system instance. + /// To support the scenario of multiple monad engines running in one process, each + /// monad engine is required to have its one help system instance. /// /// Currently each ExecutionContext has a help system instance as its member. /// @@ -73,15 +73,15 @@ namespace System.Management.Automation /// The basic contract for help providers is to provide help based on the /// search target. /// - /// The result of help provider invocation can be three things: - /// a. Full help info. (in the case of exact-match and single search result) - /// b. Short help info. (in the case of multiple search result) - /// c. Partial help info. (in the case of some commandlet help info, which + /// The result of a help provider invocation can be three things: + /// a. Full help info (in case of an exact-match and a single search result) + /// b. Short help info (in case of multiple search results) + /// c. Partial help info (in case of some cmdlet help info, which /// should be supplemented by provider help info) - /// d. Help forwarding info. (in the case of alias, which will change the target - /// for alias) + /// d. Help forwarding info (in the case of an alias, which will return the target + /// for the alias) /// - /// Help providers may need to provide functionality in following two area, + /// Help providers may need to provide functionality in the following two areas: /// a. caching and indexing to boost performance /// b. localization. /// @@ -103,11 +103,11 @@ internal HelpSystem(ExecutionContext context) Initialize(); } - private ExecutionContext _executionContext; + private readonly ExecutionContext _executionContext; /// /// ExecutionContext for the help system. Different help providers - /// will depend on this to retrieve session related information like + /// will depend on this to retrieve session-related information like /// session state and command discovery objects. /// /// @@ -121,16 +121,14 @@ internal ExecutionContext ExecutionContext #region Progress Callback - internal delegate void HelpProgressHandler(object sender, HelpProgressInfo arg); - - internal event HelpProgressHandler OnProgress; + internal event EventHandler OnProgress; #endregion #region Initialization /// - /// Initialize help system with an execution context. If the execution context + /// Initialize the help system with an execution context. If the execution context /// matches the execution context of current singleton HelpSystem object, nothing /// needs to be done. Otherwise, a new singleton HelpSystem object will be created /// with the new execution context. @@ -149,14 +147,14 @@ internal void Initialize() #region Help API /// - /// Get Help api function. This is the basic form of the Help API using help + /// Get Help API function. This is the basic form of the Help API using help /// request. /// /// Variants of this function are defined below, which will create help request - /// object on fly. + /// object on the fly. /// - /// HelpRequest object. - /// An array of HelpInfo object. + /// HelpRequest object + /// An array of HelpInfo objects internal IEnumerable GetHelp(HelpRequest helpRequest) { if (helpRequest == null) @@ -173,7 +171,7 @@ internal IEnumerable GetHelp(HelpRequest helpRequest) #region Error Handling - private Collection _lastErrors = new Collection(); + private readonly Collection _lastErrors = new Collection(); /// /// This is for tracking the last set of errors happened during the help @@ -209,17 +207,17 @@ internal HelpCategory LastHelpCategory private bool _verboseHelpErrors = false; /// - /// VerboseHelpErrors is used in the case when end user is interested - /// to know all errors happened during a help search. This property + /// VerboseHelpErrors is used in the case when end users are interested + /// to know all errors that happened during a help search. This property /// is false by default. /// /// If this property is turned on (by setting session variable "VerboseHelpError"), /// following two behaviours will be different, - /// a. Help errors will be written to error pipeline regardless the situation. + /// a. Help errors will be written to the error pipeline regardless of the situation. /// (Normally, help errors will be written to error pipeline if there is no /// help found and there is no wildcard in help search target). - /// b. Some additional warnings, including maml processing warnings, will be - /// written to error pipeline. + /// b. Some additional warnings, including MAML processing warnings, will be + /// written to the error pipeline. /// /// internal bool VerboseHelpErrors @@ -240,7 +238,7 @@ internal bool VerboseHelpErrors /// /// Gets the search paths for external snapins/modules that are currently loaded. - /// If the current shell is single-shell based, then the returned + /// If the current shell is single-shell-based, then the returned /// search path contains all the directories of currently active PSSnapIns/modules. /// /// A collection of strings representing locations. @@ -272,16 +270,16 @@ internal Collection GetSearchPaths() /// /// Get help based on the target, help type, etc /// - /// Help engine retrieve help based on following schemes, + /// Help engine retrieve help based on following schemes: /// - /// 1. if help target is empty, get default help - /// 2. if help target is not a search pattern, try to retrieve exact help + /// 1. if the help target is empty, get default help + /// 2. if the help target is not a search pattern, try to retrieve exact help /// 3. if help target is a search pattern or step 2 returns no helpInfo, try to search for help /// (Search for pattern in command name followed by pattern match in help content) - /// 4. if step 3 returns exact one helpInfo object, try to retrieve exact help. + /// 4. if step 3 returns exactly one helpInfo object, try to retrieve exact help. /// /// Help request object. - /// An array of HelpInfo object. + /// An array of HelpInfo objects private IEnumerable DoGetHelp(HelpRequest helpRequest) { _lastErrors.Clear(); @@ -323,12 +321,12 @@ private IEnumerable DoGetHelp(HelpRequest helpRequest) if (!isMatchFound) { - // Throwing exception here may not be the + // Throwing an exception here may not be the // best thing to do. Instead we can choose to // a. give a hint // b. just silently return an empty search result. // Solution: - // If it is an exact help target, throw exception. + // If it is an exact help target, throw an exception. // Otherwise, return empty result set. if (!WildcardPattern.ContainsWildcardCharacters(helpRequest.Target) && this.LastErrors.Count == 0) { @@ -343,14 +341,14 @@ private IEnumerable DoGetHelp(HelpRequest helpRequest) } /// - /// Get help that exactly match the target. + /// Get help that exactly matches the target. /// - /// If the helpInfo returned is not complete, we will forward the - /// helpInfo object to appropriate help provider for further processing. + /// If the helpInfo returned is not complete, we shall forward the + /// helpInfo object to the appropriate help provider for further processing. /// (this is implemented by ForwardHelp) /// - /// Help request object. - /// HelpInfo object retrieved. Can be Null. + /// Help request object + /// HelpInfo object retrieved (can be null) internal IEnumerable ExactMatchHelp(HelpRequest helpRequest) { bool isHelpInfoFound = false; @@ -372,7 +370,7 @@ internal IEnumerable ExactMatchHelp(HelpRequest helpRequest) // Bug Win7 737383: Win7 RTM shows both function and cmdlet help when there is // function and cmdlet with the same name. So, ignoring the ScriptCommandHelpProvider's // results and going to the CommandHelpProvider for further evaluation. - if (isHelpInfoFound && (!(helpProvider is ScriptCommandHelpProvider))) + if (isHelpInfoFound && helpProvider is not ScriptCommandHelpProvider) { // once helpInfo found from a provider..no need to traverse other providers. yield break; @@ -463,7 +461,7 @@ private IEnumerable SearchHelp(HelpRequest helpRequest) bool searchInHelpContent = false; bool shouldBreak = false; - HelpProgressInfo progress = new HelpProgressInfo(); + HelpProgressEventArgs progress = new HelpProgressEventArgs(); progress.Activity = StringUtil.Format(HelpDisplayStrings.SearchingForHelpContent, helpRequest.Target); progress.Completed = false; @@ -540,7 +538,7 @@ private IEnumerable SearchHelp(HelpRequest helpRequest) #region Help Provider Manager - private ArrayList _helpProviders = new ArrayList(); + private readonly ArrayList _helpProviders = new ArrayList(); /// /// Return the list of help providers initialized. @@ -802,11 +800,13 @@ internal void ClearScriptBlockTokenCache() /// /// Help progress info. /// - internal class HelpProgressInfo + internal class HelpProgressEventArgs : EventArgs { - internal bool Completed; - internal string Activity; - internal int PercentComplete; + internal bool Completed { get; set; } + + internal string Activity { get; set; } + + internal int PercentComplete { get; set; } } /// @@ -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 0c0770173be..ab4a39f362a 100644 --- a/src/System.Management.Automation/help/HelpUtils.cs +++ b/src/System.Management.Automation/help/HelpUtils.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation { - internal class HelpUtils + internal static class HelpUtils { private static string userHomeHelpPath = null; @@ -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 2101e268dc1..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; /// @@ -86,7 +94,7 @@ internal Collection Result /// _uniqueMatches is used to track matches already found during the search process. /// This is useful for ignoring duplicates in the case of unique search. /// - private Hashtable _uniqueMatches = new Hashtable(StringComparer.OrdinalIgnoreCase); + private readonly Hashtable _uniqueMatches = new Hashtable(StringComparer.OrdinalIgnoreCase); /// /// Search for files using the target, searchPaths member of this class. @@ -113,52 +121,11 @@ private void SearchForFiles() } } - private 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.IndexOf(pattern, StringComparison.OrdinalIgnoreCase) >= 0) - { - 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); @@ -372,4 +339,3 @@ internal enum SearchMode Unique } } - diff --git a/src/System.Management.Automation/help/MamlClassHelpInfo.cs b/src/System.Management.Automation/help/MamlClassHelpInfo.cs index 002eecb3bfe..579d72c87fe 100644 --- a/src/System.Management.Automation/help/MamlClassHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlClassHelpInfo.cs @@ -42,7 +42,7 @@ private MamlClassHelpInfo(XmlNode xmlNode, HelpCategory helpCategory) /// /// PSObject representation on help. /// - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; #region Load @@ -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 998e647cd05..8ae7b0a32cd 100644 --- a/src/System.Management.Automation/help/MamlCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/MamlCommandHelpInfo.cs @@ -98,7 +98,7 @@ internal void OverrideProviderSpecificHelpWithGenericHelp(HelpInfo genericHelpIn #region Basic Help Properties - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; /// /// Full help object for this help item. @@ -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 51fda0bc244..ecf565c942b 100644 --- a/src/System.Management.Automation/help/MamlNode.cs +++ b/src/System.Management.Automation/help/MamlNode.cs @@ -74,7 +74,7 @@ internal MamlNode(XmlNode xmlNode) _xmlNode = xmlNode; } - private XmlNode _xmlNode; + private readonly XmlNode _xmlNode; /// /// Underline xmlNode for this MamlNode object. @@ -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.. @@ -646,7 +646,7 @@ private PSObject[] GetMamlFormattingPSObjects(XmlNode xmlNode) /// /// /// - private int GetParaMamlNodeCount(XmlNodeList nodes) + private static int GetParaMamlNodeCount(XmlNodeList nodes) { int i = 0; @@ -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/MamlUtil.cs b/src/System.Management.Automation/help/MamlUtil.cs index 7ace664b728..d809655f97f 100644 --- a/src/System.Management.Automation/help/MamlUtil.cs +++ b/src/System.Management.Automation/help/MamlUtil.cs @@ -9,7 +9,7 @@ namespace System.Management.Automation /// /// The MamlUtil class. /// - internal class MamlUtil + internal static class MamlUtil { /// /// Takes Name value from maml2 and overrides it in maml1. @@ -206,7 +206,7 @@ internal static PSPropertyInfo GetPropertyInfo(PSObject psObject, string[] path) return propertyInfo; } - if (propertyInfo == null || !(propertyInfo.Value is PSObject)) + if (propertyInfo == null || propertyInfo.Value is not PSObject) { return null; } @@ -317,7 +317,7 @@ internal static void EnsurePropertyInfoPathExists(PSObject psObject, string[] pa } // If we are not on the last path element, let's make sure we can extend the path. - if (propertyInfo.Value == null || !(propertyInfo.Value is PSObject)) + if (propertyInfo.Value == null || propertyInfo.Value is not PSObject) { propertyInfo.Value = new PSObject(); } diff --git a/src/System.Management.Automation/help/PSClassHelpProvider.cs b/src/System.Management.Automation/help/PSClassHelpProvider.cs index ee65529a026..2d80b2cc863 100644 --- a/src/System.Management.Automation/help/PSClassHelpProvider.cs +++ b/src/System.Management.Automation/help/PSClassHelpProvider.cs @@ -80,11 +80,9 @@ internal override IEnumerable SearchHelp(HelpRequest helpRequest, bool else patternList.Add(target); - bool useWildCards = true; - foreach (string pattern in patternList) { - PSClassSearcher searcher = new PSClassSearcher(pattern, useWildCards, _context); + PSClassSearcher searcher = new PSClassSearcher(pattern, useWildCards: true, _context); foreach (var helpInfo in GetHelpInfo(searcher)) { @@ -108,10 +106,7 @@ internal override IEnumerable ExactMatchHelp(HelpRequest helpRequest) yield return null; } - string target = helpRequest.Target; - bool useWildCards = false; - - PSClassSearcher searcher = new PSClassSearcher(target, useWildCards, _context); + PSClassSearcher searcher = new PSClassSearcher(helpRequest.Target, useWildCards: false, _context); foreach (var helpInfo in GetHelpInfo(searcher)) { @@ -284,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/ProviderCommandHelpInfo.cs b/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs index 6d9bedf4fac..d51fb716b82 100644 --- a/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs +++ b/src/System.Management.Automation/help/ProviderCommandHelpInfo.cs @@ -13,7 +13,7 @@ internal class ProviderCommandHelpInfo : HelpInfo /// /// Help info. /// - private HelpInfo _helpInfo; + private readonly HelpInfo _helpInfo; /// /// Constructor for ProviderCommandHelpInfo. diff --git a/src/System.Management.Automation/help/ProviderContext.cs b/src/System.Management.Automation/help/ProviderContext.cs index 184ad5526fa..817e2b3f0e6 100644 --- a/src/System.Management.Automation/help/ProviderContext.cs +++ b/src/System.Management.Automation/help/ProviderContext.cs @@ -108,11 +108,9 @@ internal MamlCommandHelpInfo GetProviderSpecificHelpInfo(string helpItemName) // Does the provider know how to generate MAML. CmdletProvider cmdletProvider = providerInfo.CreateInstance(); - ICmdletProviderSupportsHelp provider = cmdletProvider as ICmdletProviderSupportsHelp; - - // Under JEA sessions the resolvedProviderPath will be null, we should allow get-help to continue. - if (provider == null) + if (!(cmdletProvider is ICmdletProviderSupportsHelp provider)) { + // Under JEA sessions the resolvedProviderPath will be null, we should allow get-help to continue. return null; } @@ -143,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 39eebe0c23a..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); @@ -136,7 +136,7 @@ internal override HelpCategory HelpCategory } } - private PSObject _fullHelpObject; + private readonly PSObject _fullHelpObject; /// /// Full help object for this provider help info. @@ -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/RemoteHelpInfo.cs b/src/System.Management.Automation/help/RemoteHelpInfo.cs index a80ba7a7736..1aa113dd86b 100644 --- a/src/System.Management.Automation/help/RemoteHelpInfo.cs +++ b/src/System.Management.Automation/help/RemoteHelpInfo.cs @@ -13,7 +13,7 @@ namespace System.Management.Automation /// internal class RemoteHelpInfo : BaseCommandHelpInfo { - private PSObject _deserializedRemoteHelp; + private readonly PSObject _deserializedRemoteHelp; internal RemoteHelpInfo( ExecutionContext context, diff --git a/src/System.Management.Automation/help/SaveHelpCommand.cs b/src/System.Management.Automation/help/SaveHelpCommand.cs index 3c4d18af00a..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; } @@ -188,7 +188,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, if (_credential != null) { - if (path.Contains("*")) + if (path.Contains('*')) { // Deal with wildcards @@ -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(); } } } @@ -484,7 +478,7 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input return inputData; } - private bool TryConvertFromDeserializedModuleInfo(object inputData, out PSModuleInfo moduleInfo) + private static bool TryConvertFromDeserializedModuleInfo(object inputData, out PSModuleInfo moduleInfo) { moduleInfo = null; PSObject pso = inputData as PSObject; 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 e9f601aba52..687faa68246 100644 --- a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs +++ b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs @@ -31,7 +31,7 @@ public class UpdatableHelpCommandBase : PSCmdlet internal bool _stopping; internal int activityId; - private Dictionary _exceptions; + private readonly Dictionary _exceptions; #region Parameters @@ -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; } @@ -102,7 +106,7 @@ public SwitchParameter UseDefaultCredentials } } - internal bool _useDefaultCredentials = false; + private bool _useDefaultCredentials = false; /// /// Forces the operation to complete. @@ -161,27 +165,27 @@ private void HandleProgressChanged(object sender, UpdatableHelpProgressEventArgs #region Constructor - private static Dictionary s_metadataCache; + private static readonly Dictionary s_metadataCache; /// /// 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 @@ -299,9 +301,9 @@ private Dictionary, UpdatableHelpModuleInfo> GetModuleInf } } - // Match wildcards - WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(new string[1] { moduleNamePattern }, wildcardOptions); + IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings( + globPatterns: new[] { moduleNamePattern }, + options: WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); foreach (KeyValuePair name in s_metadataCache) { @@ -397,7 +399,7 @@ protected override void EndProcessing() /// FullyQualifiedNames. internal void Process(IEnumerable moduleNames, IEnumerable fullyQualifiedNames) { - _helpSystem.WebClient.UseDefaultCredentials = _useDefaultCredentials; + _helpSystem.UseDefaultCredentials = _useDefaultCredentials; if (moduleNames != null) { @@ -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,13 +797,13 @@ 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)) { yield return path; - foreach (string subDirectory in Directory.GetDirectories(path)) + foreach (string subDirectory in Directory.EnumerateDirectories(path)) { foreach (string subDirectory2 in RecursiveResolvePathHelper(subDirectory)) { @@ -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 9f0c32d679a..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 != null); Debug.Assert(!string.IsNullOrEmpty(path)); Debug.Assert(!string.IsNullOrEmpty(uri)); @@ -54,7 +53,7 @@ internal Guid ModuleGuid } } - private Guid _moduleGuid; + private readonly Guid _moduleGuid; /// /// Module path. diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index 0a45b9a7605..14edabf9613 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 { /// @@ -233,14 +232,16 @@ internal UpdatableHelpProgressEventArgs(string moduleName, UpdatableHelpCommandT /// internal class UpdatableHelpSystem : IDisposable { - private TimeSpan _defaultTimeout; - private Collection _progressEvents; + private readonly TimeSpan _defaultTimeout; + private readonly Collection _progressEvents; private bool _stopping; - private object _syncObject; - private UpdatableHelpCommandBase _cmdlet; - private CancellationTokenSource _cancelTokenSource; + private readonly object _syncObject; + private readonly UpdatableHelpCommandBase _cmdlet; + private readonly CancellationTokenSource _cancelTokenSource; - internal WebClient WebClient { get; } + internal HttpClient HttpClient { get; } + + internal bool UseDefaultCredentials; internal string CurrentModule { get; set; } @@ -249,7 +250,7 @@ internal class UpdatableHelpSystem : IDisposable /// internal UpdatableHelpSystem(UpdatableHelpCommandBase cmdlet, bool useDefaultCredentials) { - WebClient = new WebClient(); + HttpClient = new HttpClient(); _defaultTimeout = new TimeSpan(0, 0, 30); _progressEvents = new Collection(); Errors = new Collection(); @@ -258,7 +259,7 @@ internal UpdatableHelpSystem(UpdatableHelpCommandBase cmdlet, bool useDefaultCre _cmdlet = cmdlet; _cancelTokenSource = new CancellationTokenSource(); - WebClient.UseDefaultCredentials = useDefaultCredentials; + UseDefaultCredentials = useDefaultCredentials; #if !CORECLR WebClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(HandleDownloadProgressChanged); @@ -275,7 +276,7 @@ public void Dispose() _completionEvent.Dispose(); #endif _cancelTokenSource.Dispose(); - WebClient.Dispose(); + HttpClient.Dispose(); GC.SuppressFinalize(this); } @@ -292,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 @@ -339,7 +334,7 @@ internal UpdatableHelpInfo GetHelpInfo(UpdatableHelpCommandType commandType, str string xml; using (HttpClientHandler handler = new HttpClientHandler()) { - handler.UseDefaultCredentials = WebClient.UseDefaultCredentials; + handler.UseDefaultCredentials = UseDefaultCredentials; using (HttpClient client = new HttpClient(handler)) { client.Timeout = _defaultTimeout; @@ -420,10 +415,11 @@ private string ResolveUri(string baseUri, bool verbose) using (HttpClientHandler handler = new HttpClientHandler()) { handler.AllowAutoRedirect = false; - handler.UseDefaultCredentials = WebClient.UseDefaultCredentials; + handler.UseDefaultCredentials = UseDefaultCredentials; using (HttpClient client = new HttpClient(handler)) { client.Timeout = new TimeSpan(0, 0, 30); // Set 30 second timeout + // codeql[cs/ssrf] - This is expected Poweshell behavior and the user assumes trust for the module they download and any URIs it references. The URIs are also not executables or scripts that would be invoked by this method. Task responseMessage = client.GetAsync(uri); using (HttpResponseMessage response = responseMessage.Result) { @@ -513,6 +509,9 @@ private string ResolveUri(string baseUri, bool verbose) private const string HelpInfoXmlNamespace = "http://schemas.microsoft.com/powershell/help/2010/05"; private const string HelpInfoXmlValidationFailure = "HelpInfoXmlValidationFailure"; + private const string MamlXmlNamespace = "http://schemas.microsoft.com/maml/2004/10"; + private const string CommandXmlNamespace = "http://schemas.microsoft.com/maml/dev/command/2004/10"; + private const string DscResourceXmlNamespace = "http://schemas.microsoft.com/maml/dev/dscResource/2004/10"; /// /// Creates a HelpInfo object. @@ -550,7 +549,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); @@ -586,12 +588,9 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo if (!string.IsNullOrEmpty(currentCulture)) { - WildcardOptions wildcardOptions = WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant; - IEnumerable patternList = SessionStateUtilities.CreateWildcardsFromStrings(new string[1] { currentCulture }, wildcardOptions); - 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)); } @@ -617,7 +616,7 @@ internal UpdatableHelpInfo CreateHelpInfo(string xml, string moduleName, Guid mo /// Xml schema. /// Validation event handler. /// HelpInfo or HelpContent? - private XmlDocument CreateValidXmlDocument(string xml, string ns, string schema, ValidationEventHandler handler, + private static XmlDocument CreateValidXmlDocument(string xml, string ns, string schema, ValidationEventHandler handler, bool helpInfo) { XmlReaderSettings settings = new XmlReaderSettings(); @@ -781,10 +780,11 @@ private bool DownloadHelpContentHttpClient(string uri, string fileName, Updatabl using (HttpClientHandler handler = new HttpClientHandler()) { handler.AllowAutoRedirect = false; - handler.UseDefaultCredentials = WebClient.UseDefaultCredentials; + handler.UseDefaultCredentials = UseDefaultCredentials; using (HttpClient client = new HttpClient(handler)) { client.Timeout = _defaultTimeout; + // codeql[cs/ssrf] - This is expected Poweshell behavior and the user assumes trust for the module they download and any URIs it references. The URIs are also not executables or scripts that would be invoked by this method. Task responseMsg = client.GetAsync(new Uri(uri), _cancelTokenSource.Token); // TODO: Should I use a continuation to write the stream to a file? @@ -994,7 +994,7 @@ internal void GenerateHelpInfo(string moduleName, Guid moduleGuid, string conten /// Removes the read only attribute. /// /// - private void RemoveReadOnly(string path) + private static void RemoveReadOnly(string path) { if (File.Exists(path)) { @@ -1002,7 +1002,7 @@ private void RemoveReadOnly(string path) if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) { - attributes = (attributes & ~FileAttributes.ReadOnly); + attributes &= ~FileAttributes.ReadOnly; File.SetAttributes(path, attributes); } } @@ -1089,16 +1089,16 @@ internal void InstallHelpContent(UpdatableHelpCommandType commandType, Execution } #if UNIX - private bool ExpandArchive(string source, string destination) + 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) { } @@ -1110,7 +1110,7 @@ private bool ExpandArchive(string source, string destination) catch (UnauthorizedAccessException) { } catch (ObjectDisposedException) { } - return sucessfulDecompression; + return successfulDecompression; } #endif @@ -1121,7 +1121,7 @@ private bool ExpandArchive(string source, string destination) /// Source path. /// Destination path. /// Is set to false if we find a single file placeholder.txt in cab. This means we no longer need to install help files. - private void UnzipHelpContent(ExecutionContext context, string srcPath, string destPath, out bool needToCopy) + private static void UnzipHelpContent(ExecutionContext context, string srcPath, string destPath, out bool needToCopy) { needToCopy = true; @@ -1131,9 +1131,9 @@ private void UnzipHelpContent(ExecutionContext context, string srcPath, string d } 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('\\')) @@ -1146,9 +1146,9 @@ private void UnzipHelpContent(ExecutionContext context, string srcPath, string d 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); @@ -1305,8 +1305,6 @@ private void ValidateAndCopyHelpContent(string sourcePath, Collection de Debug.Assert(helpItemsNode != null, "helpItemsNode must not be null"); - string targetNamespace = "http://schemas.microsoft.com/maml/2004/10"; - foreach (XmlNode node in helpItemsNode.ChildNodes) { if (node.NodeType == XmlNodeType.Element) @@ -1315,11 +1313,11 @@ private void ValidateAndCopyHelpContent(string sourcePath, Collection de { if (node.LocalName.Equals("para", StringComparison.OrdinalIgnoreCase)) { - if (!node.NamespaceURI.Equals("http://schemas.microsoft.com/maml/2004/10", StringComparison.OrdinalIgnoreCase)) + if (!node.NamespaceURI.Equals(MamlXmlNamespace, StringComparison.OrdinalIgnoreCase)) { throw new UpdatableHelpSystemException("HelpContentXmlValidationFailure", StringUtil.Format(HelpDisplayStrings.HelpContentXmlValidationFailure, - StringUtil.Format(HelpDisplayStrings.HelpContentMustBeInTargetNamespace, targetNamespace)), ErrorCategory.InvalidData, null, null); + StringUtil.Format(HelpDisplayStrings.HelpContentMustBeInTargetNamespace, MamlXmlNamespace)), ErrorCategory.InvalidData, null, null); } else { @@ -1327,16 +1325,16 @@ private void ValidateAndCopyHelpContent(string sourcePath, Collection de } } - if (!node.NamespaceURI.Equals("http://schemas.microsoft.com/maml/dev/command/2004/10", StringComparison.OrdinalIgnoreCase) && - !node.NamespaceURI.Equals("http://schemas.microsoft.com/maml/dev/dscResource/2004/10", StringComparison.OrdinalIgnoreCase)) + if (!node.NamespaceURI.Equals(CommandXmlNamespace, StringComparison.OrdinalIgnoreCase) && + !node.NamespaceURI.Equals(DscResourceXmlNamespace, StringComparison.OrdinalIgnoreCase)) { throw new UpdatableHelpSystemException("HelpContentXmlValidationFailure", StringUtil.Format(HelpDisplayStrings.HelpContentXmlValidationFailure, - StringUtil.Format(HelpDisplayStrings.HelpContentMustBeInTargetNamespace, targetNamespace)), ErrorCategory.InvalidData, null, null); + StringUtil.Format(HelpDisplayStrings.HelpContentMustBeInTargetNamespace, MamlXmlNamespace)), ErrorCategory.InvalidData, null, null); } } - CreateValidXmlDocument(node.OuterXml, targetNamespace, xsd, + CreateValidXmlDocument(node.OuterXml, MamlXmlNamespace, xsd, new ValidationEventHandler(HelpContentValidationHandler), false); } @@ -1579,8 +1577,8 @@ private void HandleDownloadProgressChanged(object sender, DownloadProgressChange /// internal class UpdatableHelpSystemDrive : IDisposable { - private string _driveName; - private PSCmdlet _cmdlet; + private readonly string _driveName; + private readonly PSCmdlet _cmdlet; /// /// Gets the drive name. diff --git a/src/System.Management.Automation/help/UpdatableHelpUri.cs b/src/System.Management.Automation/help/UpdatableHelpUri.cs index 28683a3e2a8..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 != null); 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 bccd7418476..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 @@ -348,7 +364,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, foreach (UpdatableHelpUri contentUri in newHelpInfo.HelpContentUriCollection) { - Version currentHelpVersion = (currentHelpInfo != null) ? currentHelpInfo.GetCultureVersion(contentUri.Culture) : null; + Version currentHelpVersion = currentHelpInfo?.GetCultureVersion(contentUri.Culture); string updateHelpShouldProcessAction = string.Format(CultureInfo.InvariantCulture, HelpDisplayStrings.UpdateHelpShouldProcessActionMessage, module.ModuleName, @@ -488,7 +504,7 @@ internal override bool ProcessModuleWithCulture(UpdatableHelpModuleInfo module, /// /// /// - private void ThrowPathMustBeValidContainersException(string path, Exception e) + private static void ThrowPathMustBeValidContainersException(string path, Exception e) { throw new UpdatableHelpSystemException("PathMustBeValidContainers", StringUtil.Format(HelpDisplayStrings.PathMustBeValidContainers, path), ErrorCategory.InvalidArgument, 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 23c687b58ee..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)) @@ -469,7 +468,8 @@ internal static void LogCommandLifecycleEvent(ExecutionContext executionContext, if (NeedToLogCommandLifecycleEvent(provider, executionContext)) { provider.LogCommandLifecycleEvent( - () => logContext ?? (logContext = GetLogContext(executionContext, invocationInfo)), commandState); + () => logContext ??= GetLogContext(executionContext, invocationInfo), + commandState); } } } @@ -609,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)) @@ -775,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) { @@ -808,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; } @@ -1085,7 +1085,7 @@ internal enum Severity /// Informational. /// Informational - }; + } /// /// Enum for command states. @@ -1103,7 +1103,7 @@ internal enum CommandState /// /// Terminated = 2 - }; + } /// /// Enum for provider states. @@ -1117,7 +1117,7 @@ internal enum ProviderState /// /// Stopped = 1, - }; + } #endregion } 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 d6b99155630..c51d8e35128 100644 --- a/src/System.Management.Automation/namespaces/AliasProvider.cs +++ b/src/System.Management.Automation/namespaces/AliasProvider.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation; @@ -195,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 { @@ -332,7 +327,10 @@ public class AliasProviderDynamicParameters [Parameter] public ScopedItemOptions Options { - get { return _options; } + get + { + return _options; + } set { @@ -355,4 +353,3 @@ internal bool OptionsSet private bool _optionsSet = false; } } - diff --git a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs index c70fe238b44..2a600097108 100644 --- a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs @@ -840,7 +840,7 @@ protected virtual object RenameItemDynamicParameters(string path, string newName /// /// The parameter can be any type of object that the provider can use /// to create the item. It is recommended that the provider accept at a minimum strings, and an instance - /// of the type of object that would be returned from GetItem() for this path. + /// of the type of object that would be returned from GetItem() for this path. /// can be used to convert some types to the desired type. /// /// The default implementation of this method throws an . @@ -999,7 +999,7 @@ protected virtual bool HasChildItems(string path) } /// - /// Copies an item at the specified path to an item at the . + /// Copies an item at the specified path to an item at the . /// /// /// The path of the item to copy. @@ -1084,4 +1084,3 @@ protected virtual object CopyItemDynamicParameters( #endregion ContainerCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/CoreCommandContext.cs b/src/System.Management.Automation/namespaces/CoreCommandContext.cs index 34cdb0d68b7..cf8c43c4b3e 100644 --- a/src/System.Management.Automation/namespaces/CoreCommandContext.cs +++ b/src/System.Management.Automation/namespaces/CoreCommandContext.cs @@ -29,10 +29,10 @@ 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 Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("CmdletProviderContext", "The context under which a core command is being run."); @@ -250,7 +250,6 @@ internal CmdletProviderContext( /// /// If is null. /// - internal CmdletProviderContext( CmdletProviderContext contextToCopyFrom) { @@ -295,12 +294,12 @@ internal CmdletProviderContext( /// If the constructor that takes a context to copy is /// called, this will be set to the context being copied. /// - private CmdletProviderContext _copiedContext; + private readonly CmdletProviderContext _copiedContext; /// /// The credentials under which the operation should run. /// - private PSCredential _credentials = PSCredential.Empty; + private readonly PSCredential _credentials = PSCredential.Empty; /// /// The force parameter gives guidance to providers on how vigorously they @@ -313,7 +312,7 @@ internal CmdletProviderContext( /// made visible to anyone and should only be set through the /// constructor. /// - private Cmdlet _command; + private readonly Cmdlet _command; /// /// This makes the origin of the provider request visible to the internals. @@ -328,17 +327,17 @@ internal CmdletProviderContext( /// If it is false, the objects will be accumulated until the /// GetErrorObjects method is called. /// - private bool _streamErrors; + private readonly bool _streamErrors; /// /// A collection in which objects that are written using the WriteObject(s) - /// methods are accumulated if is false. + /// methods are accumulated if is false. /// private Collection _accumulatedObjects = new Collection(); /// /// A collection in which objects that are written using the WriteError - /// method are accumulated if is false. + /// method are accumulated if is false. /// private Collection _accumulatedErrorObjects = new Collection(); @@ -391,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 @@ -557,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 @@ -583,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 @@ -622,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 @@ -671,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 @@ -775,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. @@ -789,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. @@ -811,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 @@ -1155,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 @@ -1192,4 +1146,3 @@ internal bool HasIncludeOrExclude #endregion Public methods } } - diff --git a/src/System.Management.Automation/namespaces/DriveProviderBase.cs b/src/System.Management.Automation/namespaces/DriveProviderBase.cs index d69f17e94ee..2fc44e50bdc 100644 --- a/src/System.Management.Automation/namespaces/DriveProviderBase.cs +++ b/src/System.Management.Automation/namespaces/DriveProviderBase.cs @@ -4,14 +4,12 @@ using System.Collections.ObjectModel; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation; - 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 @@ -238,4 +236,3 @@ protected virtual Collection InitializeDefaultDrives() #endregion DriveCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/EnvironmentProvider.cs b/src/System.Management.Automation/namespaces/EnvironmentProvider.cs index 4f1fd8eb0b0..1ea71d1b1ed 100644 --- a/src/System.Management.Automation/namespaces/EnvironmentProvider.cs +++ b/src/System.Management.Automation/namespaces/EnvironmentProvider.cs @@ -245,4 +245,3 @@ internal override object GetValueOfItem(object item) #endregion protected members } } - diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index 5ee65158233..d0f8f08deab 100644 --- a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs +++ b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs @@ -35,36 +35,36 @@ 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 Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("FileSystemContentStream", "The provider content reader and writer for the file system"); #endregion tracer - private string _path; - private string _streamName; - private FileMode _mode; - private FileAccess _access; - private FileShare _share; - private Encoding _encoding; - private CmdletProvider _provider; + private readonly string _path; + private readonly string _streamName; + private readonly FileMode _mode; + private readonly FileAccess _access; + private readonly FileShare _share; + private readonly Encoding _encoding; + private readonly CmdletProvider _provider; private FileStream _stream; private StreamReader _reader; private StreamWriter _writer; - private bool _usingByteEncoding; + private readonly bool _usingByteEncoding; private const char DefaultDelimiter = '\n'; - private string _delimiter = $"{DefaultDelimiter}"; - private int[] _offsetDictionary; - private bool _usingDelimiter; - private StringBuilder _currentLineContent; + private readonly string _delimiter = $"{DefaultDelimiter}"; + private readonly int[] _offsetDictionary; + private readonly bool _usingDelimiter; + private readonly StringBuilder _currentLineContent; private bool _waitForChanges; - private bool _isRawStream; + private readonly bool _isRawStream; private long _fileOffset; private FileAttributes _oldAttributes; @@ -75,7 +75,7 @@ internal class FileSystemContentReaderWriter : IContentReader, IContentWriter private bool _alreadyDetectEncoding = false; // False to add a newline to the end of the output string, true if not. - private bool _suppressNewline = false; + private readonly bool _suppressNewline = false; /// /// Constructor for the content stream. @@ -109,10 +109,26 @@ internal class FileSystemContentReaderWriter : IContentReader, IContentWriter /// Indicates raw stream. /// public FileSystemContentReaderWriter( - string path, FileMode mode, FileAccess access, - FileShare share, Encoding encoding, bool usingByteEncoding, - bool waitForChanges, CmdletProvider provider, bool isRawStream) : - this(path, null, mode, access, share, encoding, usingByteEncoding, waitForChanges, provider, isRawStream) + string path, + FileMode mode, + FileAccess access, + FileShare share, + Encoding encoding, + bool usingByteEncoding, + bool waitForChanges, + CmdletProvider provider, + bool isRawStream) + : this( + path, + streamName: null, + mode, + access, + share, + encoding, + usingByteEncoding, + waitForChanges, + provider, + isRawStream) { } @@ -397,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; } @@ -551,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 @@ -693,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. @@ -778,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(); } } @@ -903,8 +919,8 @@ private void WaitForChanges(string filePath, FileMode fileMode, FileAccess fileA { ErrorEventArgs errorEventArgs = null; var tcs = new TaskCompletionSource(); - FileSystemEventHandler onChangedHandler = (object source, FileSystemEventArgs e) => { tcs.TrySetResult(e); }; - RenamedEventHandler onRenamedHandler = (object source, RenamedEventArgs e) => { tcs.TrySetResult(e); }; + FileSystemEventHandler onChangedHandler = (object source, FileSystemEventArgs e) => tcs.TrySetResult(e); + RenamedEventHandler onRenamedHandler = (object source, RenamedEventArgs e) => tcs.TrySetResult(e); ErrorEventHandler onErrorHandler = (object source, ErrorEventArgs e) => { errorEventArgs = e; @@ -971,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(); } /// @@ -987,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(); } /// @@ -1119,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(); } } } @@ -1164,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. /// @@ -1382,7 +1361,7 @@ public override string ReadLine() } } - do + while (true) { while (_charCount > 0) { @@ -1403,7 +1382,7 @@ public override string ReadLine() line.Remove(line.Length - charsToRemove, charsToRemove); return line.ToString(); } - } while (true); + } } /// @@ -1465,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 @@ -1497,36 +1475,6 @@ _currentEncoding is ASCIIEncoding || return _byteCount; } - - private static class NativeMethods - { - // Default values - private const int MAX_DEFAULTCHAR = 2; - private const int MAX_LEADBYTES = 12; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct CPINFO - { - [MarshalAs(UnmanagedType.U4)] - internal int MaxCharSize; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEFAULTCHAR)] - public byte[] DefaultChar; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_LEADBYTES)] - public byte[] LeadBytes; - }; - - /// - /// 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 a24b48c0ef3..05c8fff76e0 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,18 +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 UNIX_ERRNO_EXDEV = 18; -#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"; @@ -71,8 +71,8 @@ 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")] - private static Dbg.PSTraceSource s_tracer = + [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"); /// @@ -90,7 +90,7 @@ public FileSystemProvider() private Collection _excludeMatcher = null; - private static System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions + private static readonly System.IO.EnumerationOptions _enumerationOptions = new System.IO.EnumerationOptions { MatchType = MatchType.Win32, MatchCasing = MatchCasing.CaseInsensitive, @@ -106,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)); } @@ -137,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)) @@ -160,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]; @@ -475,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 @@ -533,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 @@ -569,7 +569,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) if (driveIsFixed) { // Since the drive is fixed, ensure the root is valid. - validDrive = Directory.Exists(drive.Root); + validDrive = SafeDoesPathExist(drive.Root); } if (validDrive) @@ -592,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; @@ -630,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); } @@ -652,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; @@ -697,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 } /// @@ -709,7 +670,7 @@ private void WinMapNetworkDrive(PSDriveInfo drive) /// /// /// - private bool IsNetworkMappedDrive(PSDriveInfo drive) + private static bool IsNetworkMappedDrive(PSDriveInfo drive) { bool shouldMapNetworkDrive = (drive != null && !string.IsNullOrEmpty(drive.Root) && PathIsNetworkPath(drive.Root)) && (drive.Persist || (drive.Credential != null && !drive.Credential.Equals(PSCredential.Empty))); @@ -730,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 @@ -757,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 } /// @@ -789,7 +730,7 @@ private PSDriveInfo WinRemoveDrive(PSDriveInfo drive) /// PS Drive Info. /// /// True if the drive can be persisted or else false. - private bool IsSupportedDriveForPersistence(PSDriveInfo drive) + private static bool IsSupportedDriveForPersistence(PSDriveInfo drive) { bool isSupportedDriveForPersistence = false; if (drive != null && !string.IsNullOrEmpty(drive.Name) && drive.Name.Length == 1) @@ -816,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 - - StringBuilder uncBuffer = new StringBuilder(bufferSize); - driveName += ':'; + int errorCode = Interop.Windows.GetUNCForNetworkDrive(driveName[0], out uncPath); - // 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 } /// @@ -884,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) @@ -894,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. @@ -1069,7 +908,7 @@ protected override Collection InitializeDefaultDrives() if (newDrive.DriveType == DriveType.Fixed) { - if (!newDrive.RootDirectory.Exists) + if (!SafeDoesPathExist(newDrive.RootDirectory.FullName)) { continue; } @@ -1242,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; } @@ -1311,35 +1164,36 @@ protected override void GetItem(string path) // If we want to retrieve the file streams, retrieve them. if (retrieveStreams) { - if (!isContainer) + foreach (string desiredStream in dynamicParameters.Stream) { - foreach (string desiredStream in dynamicParameters.Stream) - { - // See that it matches the name specified - WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); - bool foundStream = false; + // See that it matches the name specified + WildcardPattern p = WildcardPattern.Get(desiredStream, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + bool foundStream = false; - foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName)) + foreach (AlternateStreamData stream in AlternateDataStreamUtilities.GetStreams(result.FullName)) + { + if (!p.IsMatch(stream.Stream)) { - if (!p.IsMatch(stream.Stream)) { continue; } - - string outputPath = result.FullName + ":" + stream.Stream; - WriteItemObject(stream, outputPath, isContainer); - foundStream = true; + continue; } - if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream)) - { - string errorMessage = StringUtil.Format( - FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName); - Exception e = new FileNotFoundException(errorMessage, result.FullName); - - WriteError(new ErrorRecord( - e, - "AlternateDataStreamNotFound", - ErrorCategory.ObjectNotFound, - path)); - } + string outputPath = result.FullName + ":" + stream.Stream; + // Alternate data streams can never be containers. + WriteItemObject(stream, outputPath, isContainer: false); + foundStream = true; + } + + if ((!WildcardPattern.ContainsWildcardCharacters(desiredStream)) && (!foundStream)) + { + string errorMessage = StringUtil.Format( + FileSystemProviderStrings.AlternateDataStreamNotFound, desiredStream, result.FullName); + Exception e = new FileNotFoundException(errorMessage, result.FullName); + + WriteError(new ErrorRecord( + e, + "AlternateDataStreamNotFound", + ErrorCategory.ObjectNotFound, + path)); } } } @@ -1363,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); } @@ -1373,18 +1227,46 @@ protected override void GetItem(string path) } } + private static bool SafeDoesPathExist(string rootDirectory) + { + if (Directory.Exists(rootDirectory)) + { + return true; + } + + try + { + return (File.GetAttributes(rootDirectory) & FileAttributes.Directory) is not 0; + } + // In some scenarios (like AppContainers) direct access to the root directory may + // be prevented, but more specific paths may be accessible. + catch (UnauthorizedAccessException) + { + return true; + } + catch + { + return false; + } + } + private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool showHidden) { path = NormalizePath(path); 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; @@ -1465,6 +1347,7 @@ protected override void InvokeDefaultAction(string path) if (ShouldProcess(resource, action)) { var invokeProcess = new System.Diagnostics.Process(); + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path they are specifying. If there is concern for remoting, restricted remoting guidelines should be used. invokeProcess.StartInfo.FileName = path; #if UNIX bool useShellExecute = false; @@ -1786,7 +1669,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) { @@ -1895,9 +1778,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 @@ -1911,7 +1799,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; } @@ -2019,7 +1907,7 @@ string ToModeString(FileSystemInfo fileSystemInfo) FileAttributes fileAttributes = fileSystemInfo.Attributes; bool isReparsePoint = InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(fileSystemInfo); - bool isLink = isReparsePoint || (excludeHardLink ? false : InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileSystemInfo)); + bool isLink = isReparsePoint || (!excludeHardLink && InternalSymbolicLinkLinkCodeMethods.IsHardLink(fileSystemInfo)); if (!isLink) { // special casing for the common cases - no allocations @@ -2038,15 +1926,16 @@ string ToModeString(FileSystemInfo fileSystemInfo) } } - bool isDirectory = fileAttributes.HasFlag(FileAttributes.Directory); - ReadOnlySpan mode = stackalloc char[] - { - isLink ? 'l' : isDirectory ? 'd' : '-', + ReadOnlySpan mode = + [ + isLink ? + 'l' : + fileAttributes.HasFlag(FileAttributes.Directory) ? 'd' : '-', fileAttributes.HasFlag(FileAttributes.Archive) ? 'a' : '-', fileAttributes.HasFlag(FileAttributes.ReadOnly) ? 'r' : '-', fileAttributes.HasFlag(FileAttributes.Hidden) ? 'h' : '-', fileAttributes.HasFlag(FileAttributes.System) ? 's' : '-', - }; + ]; return new string(mode); } @@ -2062,11 +1951,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; } /// @@ -2091,7 +2001,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; } @@ -2217,7 +2127,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) @@ -2236,12 +2146,12 @@ 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. /// /// - /// If is "file" then this parameter becomes the content + /// If is "file" then this parameter becomes the content /// of the file to be created. /// /// @@ -2329,7 +2239,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) @@ -2372,19 +2282,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().Slice(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; } } @@ -2427,6 +2341,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) @@ -2483,7 +2404,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 } @@ -2550,6 +2471,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; @@ -2601,40 +2529,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 { @@ -2686,26 +2609,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) { @@ -2721,7 +2639,7 @@ private enum ItemType SymbolicLink, Junction, HardLink - }; + } private static ItemType GetItemType(string input) { @@ -2772,12 +2690,6 @@ private void CreateDirectory(string path, bool streamOutput) !string.IsNullOrEmpty(path), "The caller should verify path"); - // Get the parent path - string parentPath = GetParentPath(path, null); - - // The directory name - string childName = GetChildName(path); - ErrorRecord error = null; if (!Force && ItemExists(path, out error)) { @@ -2807,7 +2719,7 @@ private void CreateDirectory(string path, bool streamOutput) if (ShouldProcess(resource, action)) { - var result = Directory.CreateDirectory(Path.Combine(parentPath, childName)); + var result = Directory.CreateDirectory(path); if (streamOutput) { @@ -2822,10 +2734,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)); } } @@ -2900,7 +2819,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) @@ -2979,6 +2898,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) { @@ -3006,7 +2938,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; @@ -3039,11 +2974,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) @@ -3103,22 +3048,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; @@ -3164,6 +3118,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 @@ -3176,6 +3132,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); + } + } } } @@ -3267,7 +3242,7 @@ private void RemoveFileSystemItem(FileSystemInfo fileSystemInfo, bool force) // if they've specified force. if (force) { - fileSystemInfo.Attributes = fileSystemInfo.Attributes & ~(FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System); + fileSystemInfo.Attributes &= ~(FileAttributes.Hidden | FileAttributes.ReadOnly | FileAttributes.System); attributeRecoveryRequired = true; } @@ -3426,12 +3401,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; } } } @@ -3664,14 +3639,73 @@ protected override void CopyItem( } else // Copy-Item local { - CopyItemLocalOrToSession(path, destinationPath, recurse, Force, null); - } - } + 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); + } + } + } _excludeMatcher.Clear(); _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()) @@ -3883,7 +3917,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) @@ -3914,7 +3948,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) @@ -3980,6 +4014,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 { @@ -3999,8 +4052,7 @@ private void CopyFileInfoItem(FileInfo file, string destinationPath, bool force, // try again FileInfo destinationItem = new FileInfo(destinationPath); - destinationItem.Attributes = - destinationItem.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + destinationItem.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); } else { @@ -4196,7 +4248,7 @@ private void RemoveFunctionsPSCopyFileFromRemoteSession(System.Management.Automa return; } - string remoteScript = @" + const string remoteScript = @" Microsoft.PowerShell.Management\Remove-Item function:PSCopyFromSessionHelper -ea SilentlyContinue -Force Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force "; @@ -4204,9 +4256,9 @@ private void RemoveFunctionsPSCopyFileFromRemoteSession(System.Management.Automa SafeInvokeCommand.Invoke(ps, this, null, false); } - private bool ValidRemoteSessionForScripting(Runspace runspace) + private static bool ValidRemoteSessionForScripting(Runspace runspace) { - if (!(runspace is RemoteRunspace)) + if (runspace is not RemoteRunspace) { return false; } @@ -4256,19 +4308,19 @@ private void SetFileMetadata(string sourceFileFullName, FileInfo destinationFile { if (string.Equals(value, "ReadOnly", StringComparison.OrdinalIgnoreCase)) { - destinationFile.Attributes = destinationFile.Attributes | FileAttributes.ReadOnly; + destinationFile.Attributes |= FileAttributes.ReadOnly; } else if (string.Equals(value, "Hidden", StringComparison.OrdinalIgnoreCase)) { - destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Hidden; + destinationFile.Attributes |= FileAttributes.Hidden; } else if (string.Equals(value, "Archive", StringComparison.OrdinalIgnoreCase)) { - destinationFile.Attributes = destinationFile.Attributes | FileAttributes.Archive; + destinationFile.Attributes |= FileAttributes.Archive; } else if (string.Equals(value, "System", StringComparison.OrdinalIgnoreCase)) { - destinationFile.Attributes = destinationFile.Attributes | FileAttributes.System; + destinationFile.Attributes |= FileAttributes.System; } } } @@ -4361,7 +4413,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf // If force is specified, and the file already exist at the destination, mask of the readonly, hidden, and system attributes if (force && File.Exists(destinationFile.FullName)) { - destinationFile.Attributes = destinationFile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System); + destinationFile.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden | FileAttributes.System); } wStream = new FileStream(destinationFile.FullName, FileMode.Create); @@ -4374,7 +4426,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf wStream = AlternateDataStreamUtilities.CreateFileStream(destinationFile.FullName, streamName, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); } #endif - long fragmentSize = FILETRANSFERSIZE; + const long fragmentSize = FILETRANSFERSIZE; long copiedSoFar = 0; long currentIndex = 0; @@ -4421,7 +4473,7 @@ private bool PerformCopyFileFromRemoteSession(string sourceFileFullName, FileInf } } - // To accomodate empty files + // To accommodate empty files string content = string.Empty; if (op["b64Fragment"] != null) { @@ -4475,10 +4527,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)) @@ -4497,7 +4546,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); @@ -4505,9 +4557,12 @@ 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; + } - string remoteScript = @" + const string remoteScript = @" Microsoft.PowerShell.Management\Remove-Item function:PSCopyToSessionHelper -ea SilentlyContinue -Force Microsoft.PowerShell.Management\Remove-Item function:PSCopyRemoteUtils -ea SilentlyContinue -Force "; @@ -4625,7 +4680,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath WriteProgress(progress); // 4MB gives the best results without spiking the resources on the remote connection. - int fragmentSize = FILETRANSFERSIZE; + const int fragmentSize = FILETRANSFERSIZE; byte[] fragment = null; int iteration = 0; bool success = false; @@ -4758,10 +4813,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath } finally { - if (fStream != null) - { - fStream.Dispose(); - } + fStream?.Dispose(); } return success; @@ -4769,7 +4821,7 @@ private bool CopyFileStreamToRemoteSession(FileInfo file, string destinationPath // Returns a hash table with metadata about this file info. // - private Hashtable GetFileMetadata(FileInfo file) + private static Hashtable GetFileMetadata(FileInfo file) { Hashtable metadata = new Hashtable(); @@ -4911,6 +4963,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 @@ -4942,30 +5005,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(':'); } /// @@ -5072,10 +5135,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); @@ -5142,7 +5202,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)) { @@ -5213,7 +5273,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; } @@ -5264,10 +5324,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); @@ -5410,7 +5467,7 @@ private string NormalizeRelativePathHelper(string path, string basePath) #if !UNIX if (!string.IsNullOrEmpty(alternateDataStream)) { - result = result + alternateDataStream; + result += alternateDataStream; } #endif @@ -5810,6 +5867,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; @@ -5857,7 +5925,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) @@ -5901,8 +5969,7 @@ private void MoveFileInfoItem( try { // mask off the readonly and hidden bits and try again - file.Attributes = - file.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + file.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); file.MoveTo(destination); @@ -5950,7 +6017,7 @@ private void MoveFileInfoItem( try { // Make sure the file is not read only - destfile.Attributes = destfile.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + destfile.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); destfile.Delete(); file.MoveTo(destination); @@ -5971,7 +6038,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 @@ -5980,13 +6047,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)); } } @@ -6022,8 +6089,7 @@ private void MoveDirectoryInfoItem( try { // mask off the readonly and hidden bits and try again - directory.Attributes = - directory.Attributes & ~(FileAttributes.ReadOnly | FileAttributes.Hidden); + directory.Attributes &= ~(FileAttributes.ReadOnly | FileAttributes.Hidden); MoveDirectoryInfoUnchecked(directory, destination, force); @@ -6058,7 +6124,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)); } } @@ -6073,35 +6139,31 @@ private void MoveDirectoryInfoItem( /// If true, force move the directory, overwriting anything at the destination. private void MoveDirectoryInfoUnchecked(DirectoryInfo directory, string destinationPath, bool force) { -#if UNIX try { if (InternalTestHooks.ThrowExdevErrorOnMoveDirectory) { - throw new IOException("Invalid cross-device link", hresult: UNIX_ERRNO_EXDEV); + throw new IOException("Invalid cross-device link"); } directory.MoveTo(destinationPath); } - catch (IOException e) when (e.HResult == UNIX_ERRNO_EXDEV) +#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 // See also: https://github.com/coreutils/coreutils/blob/439741053256618eb651e6d43919df29625b8714/src/mv.c#L212-L216 CopyAndDelete(directory, destinationPath, force); } -#else - // On Windows, being able to rename vs copy/delete a file - // is just a question of the drive - if (IsSameWindowsVolume(directory.FullName, destinationPath)) - { - directory.MoveTo(destinationPath); - } - else - { - CopyAndDelete(directory, destinationPath, force); - } -#endif } private void CopyAndDelete(DirectoryInfo directory, string destination, bool force) @@ -6139,16 +6201,6 @@ private void CopyAndDelete(DirectoryInfo directory, string destination, bool for } } -#if !UNIX - private bool IsSameWindowsVolume(string source, string destination) - { - FileInfo src = new FileInfo(source); - FileInfo dest = new FileInfo(destination); - - return (src.Directory.Root.Name == dest.Directory.Root.Name); - } -#endif - #endregion MoveItem #endregion NavigationCmdletProvider members @@ -6162,8 +6214,8 @@ private bool IsSameWindowsVolume(string source, string destination) /// /// The list of properties to get. Examples include "Attributes", "LastAccessTime," /// and other properties defined by - /// and - /// + /// and + /// /// public void GetProperty(string path, Collection providerSpecificPickList) { @@ -6211,10 +6263,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)); } @@ -6243,7 +6292,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) @@ -6543,7 +6592,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)); } } @@ -6598,7 +6647,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; @@ -6666,7 +6715,14 @@ public IContentReader GetContentReader(string path) try { - if (Directory.Exists(path)) + // Get-Content will write a non-terminating error if the target is a directory. + // On Windows, the streamName must be null or empty for it to write the error. Otherwise, the + // alternate data stream is not a directory, even if it's set on a directory. + if (Directory.Exists(path) +#if !UNIX + && string.IsNullOrEmpty(streamName) +#endif + ) { string errMsg = StringUtil.Format(SessionStateStrings.GetContainerContentException, path); ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "GetContainerContentException", ErrorCategory.InvalidOperation, null); @@ -6716,7 +6772,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) @@ -6744,7 +6800,7 @@ public IContentReader GetContentReader(string path) /// public object GetContentReaderDynamicParameters(string path) { - return new FileSystemContentReaderDynamicParameters(); + return new FileSystemContentReaderDynamicParameters(this); } /// @@ -6773,8 +6829,8 @@ 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(); - FileMode filemode = FileMode.OpenOrCreate; + Encoding encoding = Encoding.Default; + const FileMode filemode = FileMode.OpenOrCreate; string streamName = null; bool suppressNewline = false; @@ -6821,7 +6877,14 @@ public IContentWriter GetContentWriter(string path) try { - if (Directory.Exists(path)) + // Add-Content and Set-Content will write a non-terminating error if the target is a directory. + // On Windows, the streamName must be null or empty for it to write the error. Otherwise, the + // alternate data stream is not a directory, even if it's set on a directory. + if (Directory.Exists(path) +#if !UNIX + && string.IsNullOrEmpty(streamName) +#endif + ) { string errMsg = StringUtil.Format(SessionStateStrings.WriteContainerContentException, path); ErrorRecord error = new ErrorRecord(new InvalidOperationException(errMsg), "WriteContainerContentException", ErrorCategory.InvalidOperation, null); @@ -6849,7 +6912,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) @@ -6878,7 +6941,7 @@ public IContentWriter GetContentWriter(string path) /// public object GetContentWriterDynamicParameters(string path) { - return new FileSystemContentWriterDynamicParameters(); + return new FileSystemContentWriterDynamicParameters(this); } /// @@ -6899,13 +6962,6 @@ public void ClearContent(string path) path = NormalizePath(path); - if (Directory.Exists(path)) - { - string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path); - WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path)); - return; - } - try { #if !UNIX @@ -6957,6 +7013,26 @@ public void ClearContent(string path) clearStream = false; } +#endif + // On Windows, determine if our argument is a directory only after we determine if + // we're being asked to work with an alternate data stream, because directories can have + // alternate data streams on them that are not child items. These alternate data streams + // must be treated as data streams, even if they're attached to directories. However, + // if asked to work with a directory without a data stream specified, write a non-terminating + // error instead of clearing all child items of the directory. (On non-Windows, alternate + // data streams don't exist, so in that environment always write the error when addressing + // a directory.) + if (Directory.Exists(path) +#if !UNIX + && !clearStream +#endif + ) + { + string errorMsg = StringUtil.Format(SessionStateStrings.ClearDirectoryContent, path); + WriteError(new ErrorRecord(new NotSupportedException(errorMsg), "ClearDirectoryContent", ErrorCategory.InvalidOperation, path)); + return; + } +#if !UNIX if (clearStream) { FileStream fileStream = null; @@ -7002,7 +7078,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) @@ -7106,134 +7182,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 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; } @@ -7241,140 +7218,15 @@ 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 HashSet<(UInt64, UInt64)> _visitations; + private readonly HashSet<(UInt64, UInt64)> _visitations; /// /// Construct a new InodeTracker with an initial path. @@ -7521,7 +7373,7 @@ internal sealed class GetChildDynamicParameters /// Gets or sets the filter directory flag. /// [Parameter] - [Alias("ad", "d")] + [Alias("ad")] public SwitchParameter Directory { get { return _attributeDirectory; } @@ -7593,13 +7445,20 @@ public SwitchParameter System /// public class FileSystemContentDynamicParametersBase { + internal FileSystemContentDynamicParametersBase(FileSystemProvider provider) + { + _provider = provider; + } + + private readonly FileSystemProvider _provider; + /// /// Gets or sets the encoding method used when /// reading data from the file. /// [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -7610,13 +7469,19 @@ public Encoding Encoding set { + // Check for UTF-7 by checking for code page 65000 + // 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); + } _encoding = value; // If an encoding was explicitly set, be sure to capture that. WasStreamTypeSpecified = true; } } - 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. @@ -7659,6 +7524,8 @@ public class FileSystemClearContentDynamicParameters /// public class FileSystemContentWriterDynamicParameters : FileSystemContentDynamicParametersBase { + internal FileSystemContentWriterDynamicParameters(FileSystemProvider provider) : base(provider) { } + /// /// False to add a newline to the end of the output string, true if not. /// @@ -7684,6 +7551,8 @@ public SwitchParameter NoNewline /// public class FileSystemContentReaderDynamicParameters : FileSystemContentDynamicParametersBase { + internal FileSystemContentReaderDynamicParameters(FileSystemProvider provider) : base(provider) { } + /// /// Gets or sets the delimiter to use when reading the file. Custom delimiters /// may not be used when the file is opened with a "Byte" encoding. @@ -7814,17 +7683,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 ERROR_NOT_A_REPARSE_POINT = 4390; - private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; @@ -7839,62 +7699,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 { @@ -7926,25 +7730,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; @@ -7953,122 +7745,58 @@ private struct BY_HANDLE_FILE_INFORMATION public uint FileIndexLow; } - [StructLayout(LayoutKind.Sequential)] - private struct GUID + internal struct FILE_TIME { - public uint Data1; - public ushort Data2; - public ushort Data3; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Data4; + public uint dwLowDateTime; + public uint dwHighDateTime; } - [StructLayout(LayoutKind.Sequential)] - private struct REPARSE_GUID_DATA_BUFFER - { - public uint ReparseTag; - public ushort ReparseDataLength; - public ushort Reserved; - public GUID ReparseGuid; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)] - public char[] DataBuffer; - } - - [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 - { - private SafeFindHandle() : base(true) { } - - protected override bool ReleaseHandle() - { - 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, - } - - internal enum FINDEX_SEARCH_OPS : uint - { - FindExSearchNameMatch = 0x0u, - FindExSearchLimitToDirectories = 0x1u, - FindExSearchLimitToDevices = 0x2u, - FindExSearchMaxSearchOp = 0x3u, - } - - [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]; - } - /// /// 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) { if (instance.BaseObject is FileSystemInfo fileSysInfo) { -#if !UNIX - // 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(fileSysInfo.FullName, FileDesiredAccess.GenericZero)) + if (!fileSysInfo.Exists) { - string linkTarget = WinInternalGetTarget(handle); - - return linkTarget; + throw new ArgumentException( + StringUtil.Format(SessionStateStrings.PathNotFound, fileSysInfo.FullName)); } -#else - return UnixInternalGetTarget(fileSysInfo.FullName); -#endif + + return fileSysInfo.LinkTarget; + } + + return null; + } + + /// + /// Gets the target for a given file or directory, resolving symbolic links. + /// + /// The FileInfo or DirectoryInfo type. + /// The file path the instance points to. + public static string ResolvedTarget(PSObject instance) + { + if (instance.BaseObject is FileSystemInfo fileSysInfo) + { + FileSystemInfo linkTarget = fileSysInfo.ResolveLinkTarget(true); + return linkTarget is null ? fileSysInfo.FullName : linkTarget.FullName; } return null; @@ -8091,45 +7819,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(); @@ -8148,16 +7855,20 @@ private static string WinInternalGetLinkType(string filePath) // Get Buffer size IntPtr dangerousHandle = handle.DangerousGetHandle(); - bool result = DeviceIoControl(dangerousHandle, FSCTL_GET_REPARSE_POINT, - IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); + bool result = DeviceIoControl( + dangerousHandle, + FSCTL_GET_REPARSE_POINT, + InBuffer: IntPtr.Zero, + nInBufferSize: 0, + outBuffer, + outBufferSize, + out bytesReturned, + lpOverlapped: IntPtr.Zero); if (!result) { - int lastError = Marshal.GetLastWin32Error(); - if (lastError == ERROR_NOT_A_REPARSE_POINT) - linkType = null; - else - throw new Win32Exception(lastError); + // It's not a reparse point or the file system doesn't support reparse points. + return WinIsHardLink(ref dangerousHandle) ? "HardLink" : null; } REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); @@ -8172,12 +7883,8 @@ private static string WinInternalGetLinkType(string filePath) linkType = "Junction"; break; - case IO_REPARSE_TAG_APPEXECLINK: - linkType = "AppExeCLink"; - break; - default: - linkType = IsHardLink(ref dangerousHandle) ? "HardLink" : null; + linkType = null; break; } @@ -8194,13 +7901,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 } @@ -8209,65 +7931,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) @@ -8282,13 +7988,10 @@ internal static bool IsSameFileSystemItem(string pathOne, string pathTwo) #if !UNIX private static bool WinIsSameFileSystemItem(string pathOne, string pathTwo) { - var access = FileAccess.Read; - var share = FileShare.Read; - var creation = FileMode.Open; - var 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) { @@ -8321,12 +8024,9 @@ internal static bool GetInodeData(string path, out System.ValueTuple inodeData) { - var access = FileAccess.Read; - var share = FileShare.Read; - var creation = FileMode.Open; - var 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) { @@ -8345,17 +8045,10 @@ private static bool WinGetInodeData(string path, out System.ValueTuple 1); } - [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, - IntPtr.Zero, 0, outBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); + int nativeBufferSize = Marshal.SizeOf(mountPoint); + IntPtr nativeBuffer = Marshal.AllocHGlobal(nativeBufferSize); + bool success = false; - if (!result) + try { - int lastError = Marshal.GetLastWin32Error(); - if (lastError == ERROR_NOT_A_REPARSE_POINT) - return null; + Marshal.StructureToPtr(mountPoint, nativeBuffer, false); - throw new Win32Exception(lastError); - } + int bytesReturned = 0; - string targetDir = null; - - REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); - - switch (reparseDataBuffer.ReparseTag) - { - case IO_REPARSE_TAG_SYMLINK: - targetDir = Encoding.Unicode.GetString(reparseDataBuffer.PathBuffer, reparseDataBuffer.SubstituteNameOffset, reparseDataBuffer.SubstituteNameLength); - break; - - 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); - } - } - - 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 @@ -8582,7 +8170,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. @@ -8591,7 +8179,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(); @@ -8600,7 +8188,25 @@ internal static List GetStreams(string path) SafeFindHandle handle = NativeMethods.FindFirstStreamW( path, NativeMethods.StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); - if (handle.IsInvalid) throw new Win32Exception(); + + if (handle.IsInvalid) + { + int error = Marshal.GetLastWin32Error(); + + // 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 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; + } + + // An unexpected error was returned, that we don't know how to interpret. The most helpful + // thing we can do at this point is simply throw the raw Win32 exception. + throw new Win32Exception(error); + } try { @@ -8610,7 +8216,7 @@ internal static List GetStreams(string path) findStreamData.Name = findStreamData.Name.Substring(1); // And trailing :$DATA (as long as it's not the default data stream) - string dataStream = ":$DATA"; + const string dataStream = ":$DATA"; if (!string.Equals(findStreamData.Name, dataStream, StringComparison.OrdinalIgnoreCase)) { findStreamData.Name = findStreamData.Name.Replace(dataStream, string.Empty); @@ -8619,8 +8225,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(); @@ -8629,7 +8234,9 @@ internal static List GetStreams(string path) int lastError = Marshal.GetLastWin32Error(); if (lastError != NativeMethods.ERROR_HANDLE_EOF) + { throw new Win32Exception(lastError); + } } finally { handle.Dispose(); } @@ -8669,15 +8276,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) { @@ -8704,11 +8305,12 @@ 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) + if (!adjustedStreamName.StartsWith(':')) { adjustedStreamName = ":" + adjustedStreamName; } @@ -8732,14 +8334,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); @@ -8760,7 +8363,7 @@ internal static extern bool FindNextStreamW( AlternateStreamNativeData lpFindStreamData); } - internal sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid + internal sealed partial class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeFindHandle() : base(true) { } @@ -8769,9 +8372,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); } /// @@ -8808,9 +8411,9 @@ internal static class CopyFileRemoteUtils internal const string PSCopyToSessionHelperName = @"PSCopyToSessionHelper"; - private static string s_driveMaxSizeErrorFormatString = FileSystemProviderStrings.DriveMaxSizeError; - private static string s_PSCopyToSessionHelperDefinition = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]", s_driveMaxSizeErrorFormatString); - private static string s_PSCopyToSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateUserDrive()]", s_driveMaxSizeErrorFormatString); + private static readonly string s_driveMaxSizeErrorFormatString = FileSystemProviderStrings.DriveMaxSizeError; + private static readonly string s_PSCopyToSessionHelperDefinition = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]", s_driveMaxSizeErrorFormatString); + private static readonly string s_PSCopyToSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyToSessionHelperDefinitionFormat, @"[ValidateUserDrive()]", s_driveMaxSizeErrorFormatString); private const string PSCopyToSessionHelperDefinitionFormat = @" param ( @@ -9162,7 +8765,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. @@ -9259,13 +8862,13 @@ function PSCreateDirectoryOnRemoteSession }} "; - private static string s_PSCopyToSessionHelper = functionToken + PSCopyToSessionHelperName + @" + private static readonly string s_PSCopyToSessionHelper = functionToken + PSCopyToSessionHelperName + @" { " + s_PSCopyToSessionHelperDefinition + @" } "; - private static Hashtable s_PSCopyToSessionHelperFunction = new Hashtable() { + private static readonly Hashtable s_PSCopyToSessionHelperFunction = new Hashtable() { {nameToken, PSCopyToSessionHelperName}, {definitionToken, s_PSCopyToSessionHelperDefinitionRestricted} }; @@ -9276,8 +8879,8 @@ function PSCreateDirectoryOnRemoteSession internal const string PSCopyFromSessionHelperName = @"PSCopyFromSessionHelper"; - private static string s_PSCopyFromSessionHelperDefinition = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]"); - private static string s_PSCopyFromSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateUserDrive()]"); + private static readonly string s_PSCopyFromSessionHelperDefinition = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateNotNullOrEmpty()]"); + private static readonly string s_PSCopyFromSessionHelperDefinitionRestricted = StringUtil.Format(PSCopyFromSessionHelperDefinitionFormat, @"[ValidateUserDrive()]"); private const string PSCopyFromSessionHelperDefinitionFormat = @" param ( @@ -9756,7 +9359,7 @@ PSGetPathDirAndFiles @params } "; - private static Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() { + private static readonly Hashtable s_PSCopyFromSessionHelperFunction = new Hashtable() { {nameToken, PSCopyFromSessionHelperName}, {definitionToken, s_PSCopyFromSessionHelperDefinitionRestricted} }; @@ -9768,7 +9371,7 @@ PSGetPathDirAndFiles @params internal const string PSCopyRemoteUtilsName = @"PSCopyRemoteUtils"; internal static readonly string PSCopyRemoteUtilsDefinition = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateNotNullOrEmpty()]", PSValidatePathFunction); - private static string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction); + private static readonly string s_PSCopyRemoteUtilsDefinitionRestricted = StringUtil.Format(PSCopyRemoteUtilsDefinitionFormat, @"[ValidateUserDrive()]", PSValidatePathFunction); private const string PSCopyRemoteUtilsDefinitionFormat = @" param ( diff --git a/src/System.Management.Automation/namespaces/FileSystemSecurity.cs b/src/System.Management.Automation/namespaces/FileSystemSecurity.cs index 8a95b16ee8b..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)) { @@ -340,4 +342,3 @@ private static ErrorRecord CreateErrorRecord(string path, #endregion ISecurityDescriptorCmdletProvider members } } - diff --git a/src/System.Management.Automation/namespaces/FunctionProvider.cs b/src/System.Management.Automation/namespaces/FunctionProvider.cs index 1843d1edff8..ea3d9a7509c 100644 --- a/src/System.Management.Automation/namespaces/FunctionProvider.cs +++ b/src/System.Management.Automation/namespaces/FunctionProvider.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Collections.ObjectModel; using System.Management.Automation; @@ -362,7 +361,10 @@ public class FunctionProviderDynamicParameters [Parameter] public ScopedItemOptions Options { - get { return _options; } + get + { + return _options; + } set { @@ -385,4 +387,3 @@ internal bool OptionsSet private bool _optionsSet; } } - diff --git a/src/System.Management.Automation/namespaces/IContentProvider.cs b/src/System.Management.Automation/namespaces/IContentProvider.cs index 7c91019124d..b3773ca0973 100644 --- a/src/System.Management.Automation/namespaces/IContentProvider.cs +++ b/src/System.Management.Automation/namespaces/IContentProvider.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable namespace System.Management.Automation.Provider { #region IContentCmdletProvider @@ -46,7 +47,7 @@ public interface IContentCmdletProvider /// the user unless the Force property is set to true. An error should be sent to the WriteError method if /// the path represents an item that is hidden from the user and Force is set to false. /// - IContentReader GetContentReader(string path); + IContentReader? GetContentReader(string path); /// /// Gives the provider an opportunity to attach additional parameters to the @@ -63,7 +64,7 @@ public interface IContentCmdletProvider /// /// The default implementation returns null. (no additional parameters) /// - object GetContentReaderDynamicParameters(string path); + object? GetContentReaderDynamicParameters(string path); /// /// Gets the content writer for the item at the specified path. @@ -87,7 +88,7 @@ public interface IContentCmdletProvider /// the user unless the Force property is set to true. An error should be sent to the WriteError method if /// the path represents an item that is hidden from the user and Force is set to false. /// - IContentWriter GetContentWriter(string path); + IContentWriter? GetContentWriter(string path); /// /// Gives the provider an opportunity to attach additional parameters to the @@ -104,7 +105,7 @@ public interface IContentCmdletProvider /// /// The default implementation returns null. (no additional parameters) /// - object GetContentWriterDynamicParameters(string path); + object? GetContentWriterDynamicParameters(string path); /// /// Clears the content from the specified item. @@ -141,9 +142,8 @@ public interface IContentCmdletProvider /// /// The default implementation returns null. (no additional parameters) /// - object ClearContentDynamicParameters(string path); + object? ClearContentDynamicParameters(string path); } #endregion IContentCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/IContentReader.cs b/src/System.Management.Automation/namespaces/IContentReader.cs index 8835d948ddd..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 @@ -65,4 +66,3 @@ public interface IContentReader : IDisposable #endregion IContentReader } - diff --git a/src/System.Management.Automation/namespaces/IContentWriter.cs b/src/System.Management.Automation/namespaces/IContentWriter.cs index e01ed0c9cb4..621808af80c 100644 --- a/src/System.Management.Automation/namespaces/IContentWriter.cs +++ b/src/System.Management.Automation/namespaces/IContentWriter.cs @@ -4,6 +4,7 @@ using System.Collections; using System.IO; +#nullable enable namespace System.Management.Automation.Provider { #region IContentWriter @@ -66,4 +67,3 @@ public interface IContentWriter : IDisposable #endregion IContentWriter } - diff --git a/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs b/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs index d7265e7380f..232f3f5d78b 100644 --- a/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs +++ b/src/System.Management.Automation/namespaces/IDynamicPropertyProvider.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable namespace System.Management.Automation.Provider { #region IDynamicPropertyCmdletProvider - /// /// An interface that can be implemented on a Cmdlet provider to expose the dynamic /// manipulation of properties. @@ -61,7 +61,7 @@ void NewProperty( string path, string propertyName, string propertyTypeName, - object value); + object? value); /// /// Gives the provider an opportunity to attach additional parameters to the @@ -87,11 +87,11 @@ void NewProperty( /// /// The default implementation returns null. (no additional parameters) /// - object NewPropertyDynamicParameters( + object? NewPropertyDynamicParameters( string path, string propertyName, string propertyTypeName, - object value); + object? value); /// /// Removes a property on the item specified by the path. @@ -196,7 +196,7 @@ void RenameProperty( /// /// The default implementation returns null. (no additional parameters) /// - object RenamePropertyDynamicParameters( + object? RenamePropertyDynamicParameters( string path, string sourceProperty, string destinationProperty); @@ -262,7 +262,7 @@ void CopyProperty( /// /// The default implementation returns null. (no additional parameters) /// - object CopyPropertyDynamicParameters( + object? CopyPropertyDynamicParameters( string sourcePath, string sourceProperty, string destinationPath, @@ -328,7 +328,7 @@ void MoveProperty( /// /// The default implementation returns null. (no additional parameters) /// - object MovePropertyDynamicParameters( + object? MovePropertyDynamicParameters( string sourcePath, string sourceProperty, string destinationPath, @@ -337,4 +337,3 @@ object MovePropertyDynamicParameters( #endregion IDynamicPropertyCmdletProvider } - 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/IPropertiesProvider.cs b/src/System.Management.Automation/namespaces/IPropertiesProvider.cs index 2a347fafbce..1f4a6c168fe 100644 --- a/src/System.Management.Automation/namespaces/IPropertiesProvider.cs +++ b/src/System.Management.Automation/namespaces/IPropertiesProvider.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; +#nullable enable namespace System.Management.Automation.Provider { #region IPropertyCmdletProvider @@ -58,7 +59,7 @@ public interface IPropertyCmdletProvider /// void GetProperty( string path, - Collection providerSpecificPickList); + Collection? providerSpecificPickList); /// /// Gives the provider an opportunity to attach additional parameters to the @@ -79,9 +80,9 @@ void GetProperty( /// /// The default implementation returns null. (no additional parameters) /// - object GetPropertyDynamicParameters( + object? GetPropertyDynamicParameters( string path, - Collection providerSpecificPickList); + Collection? providerSpecificPickList); /// /// Sets the specified properties of the item at the specified path. @@ -137,7 +138,7 @@ void SetProperty( /// /// The default implementation returns null. (no additional parameters) /// - object SetPropertyDynamicParameters( + object? SetPropertyDynamicParameters( string path, PSObject propertyValue); @@ -191,11 +192,10 @@ void ClearProperty( /// /// The default implementation returns null. (no additional parameters) /// - object ClearPropertyDynamicParameters( + object? ClearPropertyDynamicParameters( string path, Collection propertyToClear); } #endregion IPropertyCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/ItemProviderBase.cs b/src/System.Management.Automation/namespaces/ItemProviderBase.cs index c563f48aaec..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 @@ -688,4 +688,3 @@ protected virtual string[] ExpandPath(string path) #endregion ItemCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index d5e60b652c9..65c28f1586d 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -22,20 +22,20 @@ 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 Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("LocationGlobber", "The location globber converts PowerShell paths with glob characters to zero or more paths."); /// /// User level tracing for path resolution. /// - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "PathResolution", "Traces the path resolution algorithm.")] - private static Dbg.PSTraceSource s_pathResolutionTracer = + private static readonly Dbg.PSTraceSource s_pathResolutionTracer = Dbg.PSTraceSource.GetTracer( "PathResolution", "Traces the path resolution algorithm.", @@ -52,7 +52,7 @@ internal sealed class LocationGlobber /// The instance of session state on which this location globber acts. /// /// - /// If is null. + /// If is null. /// internal LocationGlobber(SessionState sessionState) { @@ -1654,7 +1654,7 @@ internal bool IsAbsolutePath(string path, out string driveName) /// /// The instance of session state on which this globber acts. /// - private SessionState _sessionState; + private readonly SessionState _sessionState; /// /// Removes the back tick "`" from any of the glob characters in the path. @@ -1666,7 +1666,7 @@ internal bool IsAbsolutePath(string path, out string driveName) /// The path with the glob characters unescaped. /// /// - /// If is null. + /// If is null. /// private static string RemoveGlobEscaping(string path) { @@ -1699,7 +1699,7 @@ private static string RemoveGlobEscaping(string path) /// false otherwise. /// /// - /// If is null. + /// If is null. /// /// /// The comparison is done using a case-insensitive comparison using the @@ -1763,7 +1763,7 @@ internal bool IsShellVirtualDrive(string driveName, out SessionStateScope scope) /// /// /// This out parameter returns the drive that was specified - /// by the . If is + /// by the . If is /// an absolute path this value may be something other than /// the current working drive. /// @@ -1789,7 +1789,7 @@ internal bool IsShellVirtualDrive(string driveName, out SessionStateScope scope) /// This is internal so that it can be called from SessionState /// /// - /// If is null. + /// If is null. /// /// /// If the refers to a drive that could not be found. @@ -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 @@ -2272,7 +2275,7 @@ internal string GenerateRelativePath( return driveRootRelativeWorkingPath; } - private bool HasRelativePathTokens(string path) + private static bool HasRelativePathTokens(string path) { string comparePath = path.Replace('/', '\\'); @@ -2417,7 +2420,7 @@ private static string ParseProviderPath(string path, out string providerId) /// paths. Instead an appropriate path will be returned as if it did exist. /// /// - /// The provider that will be used to glob the . + /// The provider that will be used to glob the . /// /// /// The context under which the command is occurring. @@ -3273,27 +3276,27 @@ internal static string RemoveProviderQualifier(string path) /// /// Generates a collection of containers and/or leaves that are children of the containers /// in the currentDirs parameter and match the glob expression in the - /// parameter. + /// parameter. /// /// /// A collection of paths that should be searched for leaves that match the - /// expression. + /// expression. /// /// /// The drive the Msh path is relative to. /// /// /// A single element of a path that may or may not contain a glob expression. This parameter - /// is used to search the containers in for children that + /// is used to search the containers in for children that /// match the glob expression. /// /// - /// True if the is the last element to glob over. If false, we + /// True if the is the last element to glob over. If false, we /// need to get all container names from the provider even if they don't match the filter. /// /// /// The provider associated with the paths that are being passed in the - /// and parameters. + /// and parameters. /// The provider must derive from ContainerCmdletProvider or NavigationCmdletProvider /// in order to get globbing. /// @@ -3302,10 +3305,10 @@ internal static string RemoveProviderQualifier(string path) /// /// /// A collection of fully qualified namespace paths whose leaf element matches the - /// expression. + /// expression. /// /// - /// If or + /// If or /// is null. /// /// @@ -3892,24 +3895,24 @@ internal Collection ExpandGlobPath( /// /// Generates a collection of containers and/or leaves that are children of the containers /// in the currentDirs parameter and match the glob expression in the - /// parameter. + /// parameter. /// /// /// A collection of paths that should be searched for leaves that match the - /// expression. + /// expression. /// /// /// A single element of a path that may or may not contain a glob expression. This parameter - /// is used to search the containers in for children that + /// is used to search the containers in for children that /// match the glob expression. /// /// - /// True if the is the last element to glob over. If false, we + /// True if the is the last element to glob over. If false, we /// need to get all container names from the provider even if they don't match the filter. /// /// /// The provider associated with the paths that are being passed in the - /// and parameters. + /// and parameters. /// The provider must derive from ContainerCmdletProvider or NavigationCmdletProvider /// in order to get globbing. /// @@ -3918,10 +3921,10 @@ internal Collection ExpandGlobPath( /// /// /// A collection of fully qualified namespace paths whose leaf element matches the - /// expression. + /// expression. /// /// - /// If or + /// If or /// is null. /// /// @@ -4536,7 +4539,7 @@ internal static bool IsHomePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Support the single "~" if (path.Length == 1) @@ -4635,7 +4638,7 @@ internal string GetHomeRelativePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Strip of the ~ and the \ or / if present @@ -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 42dd3b32b36..cf656e633a3 100644 --- a/src/System.Management.Automation/namespaces/NavigationProviderBase.cs +++ b/src/System.Management.Automation/namespaces/NavigationProviderBase.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Management.Automation.Internal; -using System.Text; namespace System.Management.Automation.Provider { @@ -516,10 +515,7 @@ internal string ContractRelativePath( return string.Empty; } - if (basePath == null) - { - basePath = string.Empty; - } + basePath ??= string.Empty; providerBaseTracer.WriteLine("basePath = {0}", basePath); @@ -651,7 +647,7 @@ internal string ContractRelativePath( if (originalPathHadTrailingSlash) { - result = result + StringLiterals.DefaultPathSeparator; + result += StringLiterals.DefaultPathSeparator; } return result; @@ -1037,8 +1033,7 @@ private static Stack NormalizeThePath( if (!allowNonExistingPaths) { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( + (PSArgumentException)PSTraceSource.NewArgumentException( nameof(path), SessionStateStrings.NormalizeRelativePathOutsideBase, path, @@ -1094,4 +1089,3 @@ private string CreateNormalizedRelativePathFromStack(Stack normalizedPat #endregion NavigationCmdletProvider } - diff --git a/src/System.Management.Automation/namespaces/PathInfo.cs b/src/System.Management.Automation/namespaces/PathInfo.cs index cd91845f84c..0c5483ceffa 100644 --- a/src/System.Management.Automation/namespaces/PathInfo.cs +++ b/src/System.Management.Automation/namespaces/PathInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using Dbg = System.Management.Automation; - namespace System.Management.Automation { /// @@ -78,10 +76,10 @@ public string ProviderPath } private string _providerPath; - private SessionState _sessionState; + private readonly SessionState _sessionState; /// - /// Gets the MSH path that this object represents. + /// Gets the PowerShell path that this object represents. /// public string Path { @@ -91,15 +89,15 @@ public string Path } } - private PSDriveInfo _drive; - private ProviderInfo _provider; - private string _path = string.Empty; + private readonly PSDriveInfo _drive; + private readonly ProviderInfo _provider; + 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 326d4d51b04..4345ec9f8ec 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -9,17 +9,18 @@ using System.Management.Automation.Runspaces; using System.Management.Automation.Internal; using System.Management.Automation.Host; -using System.Reflection; using System.Resources; using System.Diagnostics.CodeAnalysis; // for fxcop using System.Security.AccessControl; 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 { /// @@ -38,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 /// @@ -75,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( @@ -222,7 +223,7 @@ internal object StartDynamicParameters(CmdletProviderContext cmdletProviderConte /// /// /// The context under which this method is being called. - /// + /// internal void Stop(CmdletProviderContext cmdletProviderContext) { Context = cmdletProviderContext; @@ -259,9 +260,7 @@ internal void GetProperty( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -299,9 +298,7 @@ internal object GetPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -330,9 +327,7 @@ internal void SetProperty( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -370,9 +365,7 @@ internal object SetPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -404,9 +397,7 @@ internal void ClearProperty( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -444,9 +435,7 @@ internal object ClearPropertyDynamicParameters( { Context = cmdletProviderContext; - IPropertyCmdletProvider propertyProvider = this as IPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -490,9 +479,7 @@ internal void NewProperty( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -537,9 +524,7 @@ internal object NewPropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -571,9 +556,7 @@ internal void RemoveProperty( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -610,9 +593,7 @@ internal object RemovePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -648,9 +629,7 @@ internal void RenameProperty( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -691,9 +670,7 @@ internal object RenamePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -733,9 +710,7 @@ internal void CopyProperty( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -780,9 +755,7 @@ internal object CopyPropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -822,9 +795,7 @@ internal void MoveProperty( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -869,9 +840,7 @@ internal object MovePropertyDynamicParameters( { Context = cmdletProviderContext; - IDynamicPropertyCmdletProvider propertyProvider = this as IDynamicPropertyCmdletProvider; - - if (propertyProvider == null) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -902,9 +871,7 @@ internal IContentReader GetContentReader( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -937,9 +904,7 @@ internal object GetContentReaderDynamicParameters( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -966,9 +931,7 @@ internal IContentWriter GetContentWriter( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1001,9 +964,7 @@ internal object GetContentWriterDynamicParameters( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -1027,9 +988,7 @@ internal void ClearContent( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1062,9 +1021,7 @@ internal object ClearContentDynamicParameters( { Context = cmdletProviderContext; - IContentCmdletProvider contentProvider = this as IContentCmdletProvider; - - if (contentProvider == null) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -1395,7 +1352,7 @@ public PSHost Host /// public virtual char AltItemSeparator => #if UNIX - Utils.Separators.Backslash[0]; + '\\'; #else Path.AltDirectorySeparatorChar; #endif @@ -1462,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()) @@ -1865,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); @@ -2026,4 +1984,3 @@ public void WriteError(ErrorRecord errorRecord) } #pragma warning restore 56506 - 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 dbc03a69181..cd38b92c177 100644 --- a/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs +++ b/src/System.Management.Automation/namespaces/ProviderDeclarationAttribute.cs @@ -52,7 +52,7 @@ public CmdletProviderAttribute( ProviderCapabilities = providerCapabilities; } - private char[] _illegalCharacters = new char[] { ':', '\\', '[', ']', '?', '*' }; + private readonly char[] _illegalCharacters = new char[] { ':', '\\', '[', ']', '?', '*' }; /// /// Gets the name of the provider. @@ -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 ff0f5780771..4e3b84716cd 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #if !UNIX using System; @@ -27,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" /// @@ -60,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, @@ -75,7 +79,7 @@ public sealed partial class RegistryProvider : [Dbg.TraceSourceAttribute( "RegistryProvider", "The namespace navigation provider for the Windows Registry")] - private static Dbg.PSTraceSource s_tracer = + private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("RegistryProvider", "The namespace navigation provider for the Windows Registry"); @@ -782,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()) { @@ -797,8 +801,8 @@ private static string EscapeSpecialChars(string path) // should not be done. if (textElement.Contains(charactersThatNeedEscaping)) { - // This text element needs espacing - result.Append("`"); + // This text element needs escaping + result.Append('`'); } result.Append(textElement); @@ -831,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()) { @@ -846,8 +850,8 @@ private static string EscapeChildName(string name) // should not be done. if (textElement.Contains(charactersThatNeedEscaping)) { - // This text element needs espacing - result.Append("`"); + // This text element needs escaping + result.Append('`'); } result.Append(textElement); @@ -1517,7 +1521,7 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( bool result = false; - do + while (true) { // See if the paths are equal @@ -1550,7 +1554,7 @@ private bool ErrorIfDestinationIsSourceOrChildOfSource( } destinationPath = newDestinationPath; - } while (true); + } if (result) { @@ -1818,11 +1822,22 @@ public void GetProperty( // as the property name when adding the note, as // PSObject does not allow an empty propertyName - notePropertyName = GetLocalizedDefaultToken(); + 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(); @@ -2023,7 +2038,7 @@ public void ClearProperty( string propertyNameToAdd = valueName; if (string.IsNullOrEmpty(valueName)) { - propertyNameToAdd = GetLocalizedDefaultToken(); + propertyNameToAdd = LocalizedDefaultToken; } result.Properties.Add(new PSNoteProperty(propertyNameToAdd, defaultValue)); @@ -2955,7 +2970,7 @@ private string NormalizePath(string path) return result; } - private bool HasRelativePathTokens(string path) + private static bool HasRelativePathTokens(string path) { return ( path.StartsWith('\\') || @@ -2992,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) { @@ -3055,13 +3067,13 @@ private void GetFilteredRegistryKeyProperties(string path, if (!string.IsNullOrEmpty(requestedValueName)) { - valueNameToMatch = GetLocalizedDefaultToken(); + valueNameToMatch = LocalizedDefaultToken; } } if ( expandAll || - ((Context.SuppressWildcardExpansion == false) && (valueNameMatcher.IsMatch(valueNameToMatch))) || + ((!Context.SuppressWildcardExpansion) && (valueNameMatcher.IsMatch(valueNameToMatch))) || ((Context.SuppressWildcardExpansion) && (string.Equals(valueNameToMatch, requestedValueName, StringComparison.OrdinalIgnoreCase)))) { if (string.IsNullOrEmpty(valueNameToMatch)) @@ -3070,7 +3082,7 @@ private void GetFilteredRegistryKeyProperties(string path, // as the property name when adding the note, as // PSObject does not allow an empty propertyName - valueNameToMatch = GetLocalizedDefaultToken(); + valueNameToMatch = LocalizedDefaultToken; } hadAMatch = true; @@ -3194,7 +3206,7 @@ private object ResetRegistryKeyValue(IRegistryWrapper key, string valueName) /// /// true if the path is empty, a \ or a /, else false /// - private bool IsHiveContainer(string path) + private static bool IsHiveContainer(string path) { bool result = false; if (path == null) @@ -3782,7 +3794,7 @@ private void WriteWrappedPropertyObject(object value, string propertyName, strin string propertyNameToAdd = propertyName; if (string.IsNullOrEmpty(propertyName)) { - propertyNameToAdd = GetLocalizedDefaultToken(); + propertyNameToAdd = LocalizedDefaultToken; } result.Properties.Add(new PSNoteProperty(propertyNameToAdd, value)); @@ -3813,7 +3825,7 @@ private static object ConvertValueToKind(object value, RegistryValueKind kind) value, typeof(byte[]), CultureInfo.CurrentCulture) - : new byte[] { }; + : Array.Empty(); break; case RegistryValueKind.DWord: @@ -3833,7 +3845,9 @@ private static object ConvertValueToKind(object value, RegistryValueKind kind) { value = 0; } - } break; + } + + break; case RegistryValueKind.ExpandString: value = (value != null) @@ -3850,7 +3864,7 @@ private static object ConvertValueToKind(object value, RegistryValueKind kind) value, typeof(string[]), CultureInfo.CurrentCulture) - : new string[] { }; + : Array.Empty(); break; case RegistryValueKind.QWord: @@ -3870,7 +3884,9 @@ private static object ConvertValueToKind(object value, RegistryValueKind kind) { value = 0; } - } break; + } + + break; case RegistryValueKind.String: value = (value != null) @@ -4028,7 +4044,7 @@ private void WriteRegistryItemObject( if (string.IsNullOrEmpty(valueNames[index])) { // The first unnamed value becomes the default value - valueNames[index] = GetLocalizedDefaultToken(); + valueNames[index] = LocalizedDefaultToken; break; } } @@ -4103,16 +4119,13 @@ private bool ParseKind(string type, out RegistryValueKind kind) /// Gets the default value name token from the resource. /// In English that token is "(default)" without the quotes. /// + /// + /// This should not be localized as it will break scripts. + /// /// /// A string containing the default value name. /// - private string GetLocalizedDefaultToken() - { - // This shouldn't be localized as it will break scripts - - string defaultValueName = "(default)"; - return defaultValueName; - } + private static string LocalizedDefaultToken => "(default)"; /// /// Converts an empty or null userEnteredPropertyName to the localized @@ -4135,7 +4148,7 @@ private string GetPropertyName(string userEnteredPropertyName) if (stringComparer.Compare( userEnteredPropertyName, - GetLocalizedDefaultToken(), + LocalizedDefaultToken, CompareOptions.IgnoreCase) == 0) { result = null; diff --git a/src/System.Management.Automation/namespaces/RegistrySecurity.cs b/src/System.Management.Automation/namespaces/RegistrySecurity.cs index 04f25d7f06f..9308c50dddc 100644 --- a/src/System.Management.Automation/namespaces/RegistrySecurity.cs +++ b/src/System.Management.Automation/namespaces/RegistrySecurity.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #if !UNIX -using System; using System.Management.Automation; using System.Management.Automation.Provider; using System.Security.AccessControl; @@ -164,7 +164,7 @@ public void SetSecurityDescriptor( /// Specifies the parts of a security descriptor to create. /// /// - /// An instance of object. + /// An instance of object. /// /// and are not used by this method. public ObjectSecurity NewSecurityDescriptorFromPath( @@ -191,7 +191,7 @@ public ObjectSecurity NewSecurityDescriptorFromPath( /// Specifies the parts of a security descriptor to create. /// /// - /// An instance of object. + /// An instance of object. /// public ObjectSecurity NewSecurityDescriptorOfType( string type, diff --git a/src/System.Management.Automation/namespaces/RegistryWrapper.cs b/src/System.Management.Automation/namespaces/RegistryWrapper.cs index 6f0c3e3d66f..ebdbe833839 100644 --- a/src/System.Management.Automation/namespaces/RegistryWrapper.cs +++ b/src/System.Management.Automation/namespaces/RegistryWrapper.cs @@ -17,30 +17,45 @@ namespace Microsoft.PowerShell.Commands { + +#nullable enable internal interface IRegistryWrapper { - void SetValue(string name, object value); - void SetValue(string name, object value, RegistryValueKind valueKind); + void SetValue(string? name, object value); + + void SetValue(string? name, object value, RegistryValueKind valueKind); + string[] GetValueNames(); + void DeleteValue(string name); + string[] GetSubKeyNames(); - IRegistryWrapper CreateSubKey(string subkey); - IRegistryWrapper OpenSubKey(string name, bool writable); + + IRegistryWrapper? CreateSubKey(string subkey); + + IRegistryWrapper? OpenSubKey(string name, bool writable); + void DeleteSubKeyTree(string subkey); - object GetValue(string name); - object GetValue(string name, object defaultValue, RegistryValueOptions options); - RegistryValueKind GetValueKind(string name); + + object? GetValue(string? name); + + object? GetValue(string? name, object? defaultValue, RegistryValueOptions options); + + RegistryValueKind GetValueKind(string? name); object RegistryKey { get; } void SetAccessControl(ObjectSecurity securityDescriptor); + ObjectSecurity GetAccessControl(AccessControlSections includeSections); + void Close(); string Name { get; } int SubKeyCount { get; } } +#nullable restore internal static class RegistryWrapperUtils { @@ -114,7 +129,7 @@ public static object ConvertUIntToValueForRegistryIfNeeded(object value, Registr internal class RegistryWrapper : IRegistryWrapper { - private RegistryKey _regKey; + private readonly RegistryKey _regKey; internal RegistryWrapper(RegistryKey regKey) { @@ -246,8 +261,8 @@ public ObjectSecurity GetAccessControl(AccessControlSections includeSections) internal class TransactedRegistryWrapper : IRegistryWrapper { - private TransactedRegistryKey _txRegKey; - private CmdletProvider _provider; + private readonly TransactedRegistryKey _txRegKey; + private readonly CmdletProvider _provider; internal TransactedRegistryWrapper(TransactedRegistryKey txRegKey, CmdletProvider provider) { diff --git a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs b/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs deleted file mode 100644 index 3d9e299d028..00000000000 --- a/src/System.Management.Automation/namespaces/SafeRegistryHandle.cs +++ /dev/null @@ -1,56 +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. - - [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] - internal SafeRegistryHandle() : base(true) { } - - [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] - internal SafeRegistryHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) - { - SetHandle(preexistingHandle); - } - - [DllImport(PinvokeDllNames.RegCloseKeyDllName), - SuppressUnmanagedCodeSecurity, - ResourceExposure(ResourceScope.None), - ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - 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 33b043ce919..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 = @@ -326,7 +326,7 @@ protected override void GetChildItems(string path, bool recurse) } sortedEntries.Sort( - delegate (DictionaryEntry left, DictionaryEntry right) + (DictionaryEntry left, DictionaryEntry right) => { string leftKey = (string)left.Key; string rightKey = (string)right.Key; @@ -708,8 +708,7 @@ protected override void NewItem(string path, string type, object newItem) if (ItemExists(path) && !Force) { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( + (PSArgumentException)PSTraceSource.NewArgumentException( nameof(path), SessionStateStrings.NewItemAlreadyExists, path); @@ -838,8 +837,7 @@ protected override void CopyItem(string path, string copyPath, bool recurse) else { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( + (PSArgumentException)PSTraceSource.NewArgumentException( nameof(path), SessionStateStrings.CopyItemDoesntExist, path); @@ -897,8 +895,7 @@ protected override void RenameItem(string name, string newName) if (ItemExists(newName) && !Force) { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( + (PSArgumentException)PSTraceSource.NewArgumentException( nameof(newName), SessionStateStrings.NewItemAlreadyExists, newName); @@ -984,8 +981,7 @@ protected override void RenameItem(string name, string newName) else { PSArgumentException e = - (PSArgumentException) - PSTraceSource.NewArgumentException( + (PSArgumentException)PSTraceSource.NewArgumentException( nameof(name), SessionStateStrings.RenameItemDoesntExist, name); @@ -1116,8 +1112,8 @@ internal SessionStateProviderBaseContentReaderWriter(string path, SessionStatePr _provider = provider; } - private string _path; - private SessionStateProviderBase _provider; + private readonly string _path; + private readonly SessionStateProviderBase _provider; /// /// Reads the content from the item. diff --git a/src/System.Management.Automation/namespaces/TransactedRegistry.cs b/src/System.Management.Automation/namespaces/TransactedRegistry.cs index 56062e6de6c..6465ea1819c 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistry.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistry.cs @@ -42,7 +42,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction. /// - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey CurrentUser = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CURRENT_USER); @@ -61,7 +60,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction. /// - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey LocalMachine = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_LOCAL_MACHINE); @@ -80,7 +78,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction. /// - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey ClassesRoot = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CLASSES_ROOT); @@ -99,7 +96,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction. /// - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey Users = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_USERS); @@ -118,7 +114,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction. /// - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey CurrentConfig = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CURRENT_CONFIG); diff --git a/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs index dac6f6c0897..d899d9257b4 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs @@ -141,9 +141,6 @@ public sealed class TransactedRegistryKey : MarshalByRefObject, IDisposable // If that call fails with ERROR_INVALID_TRANSACTION, we have possibly run into bug 181242. To workaround // this, we open the key without a transaction and then open it again with // a transaction and return THAT hkey. - - // Suppressed because there is no way for arbitrary data to be passed. - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] private int RegOpenKeyTransactedWrapper(SafeRegistryHandle hKey, string lpSubKey, int ulOptions, int samDesired, out SafeRegistryHandle hkResult, SafeTransactionHandle hTransaction, IntPtr pExtendedParameter) @@ -341,13 +338,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 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey CreateSubKey(string subkey) @@ -358,16 +353,14 @@ 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)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCheck permissionCheck) @@ -378,17 +371,15 @@ 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)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public unsafe TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCheck permissionCheck, TransactedRegistrySecurity registrySecurity) @@ -397,8 +388,6 @@ public unsafe TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermi } [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private unsafe TransactedRegistryKey CreateSubKeyInternal(string subkey, RegistryKeyPermissionCheck permissionCheck, object registrySecurityObj) @@ -486,11 +475,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. /// - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] + /// The subkey to delete. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKey(string subkey) @@ -502,16 +489,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. /// - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] + /// 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. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKey(string subkey, bool throwOnMissingSubKey) @@ -567,10 +552,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. /// - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] + /// The subkey to delete. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKeyTree(string subkey) @@ -613,7 +596,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 { @@ -623,8 +609,6 @@ public void DeleteSubKeyTree(string subkey) // An internal version which does no security checks or argument checking. Skipping the // security checks should give us a slight perf gain on large trees. - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private void DeleteSubKeyTreeInternal(string subkey) @@ -653,7 +637,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,10 +651,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. /// - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + /// Name of the value to delete. public void DeleteValue(string name) { DeleteValue(name, true); @@ -676,13 +661,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) { EnsureWriteable(); @@ -748,12 +731,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 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, bool writable) @@ -791,14 +772,12 @@ 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)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck) @@ -810,15 +789,13 @@ 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)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, RegistryRights rights) @@ -826,8 +803,6 @@ public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck return InternalOpenSubKey(name, permissionCheck, (int)rights); } - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private TransactedRegistryKey InternalOpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, int rights) @@ -868,8 +843,6 @@ private TransactedRegistryKey InternalOpenSubKey(string name, RegistryKeyPermiss // This required no security checks. This is to get around the Deleting SubKeys which only require // write permission. They call OpenSubKey which required read. Now instead call this function w/o security checks - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] internal TransactedRegistryKey InternalOpenSubKey(string name, bool writable) @@ -897,11 +870,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. /// - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] + /// The subkey requested or null if the operation failed. + /// Name or path of the subkey to open. // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name) @@ -912,8 +883,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 +926,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 +973,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 +1010,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 +1078,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 +1092,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 +1107,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 +1278,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 +1302,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 +1324,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 +1342,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 +1352,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 +1569,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 +1580,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 +1591,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 +1605,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 +2001,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 e0cd273fe2b..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) { @@ -190,7 +190,6 @@ public TransactedRegistrySecurity() // The name of registry key must start with a predefined string, // like CLASSES_ROOT, CURRENT_USER, MACHINE, and USERS. See // MSDN's help for SetNamedSecurityInfo for details. - [SecurityPermission(SecurityAction.Assert, UnmanagedCode=true)] internal TransactedRegistrySecurity(string name, AccessControlSections includeSections) : base(true, ResourceType.RegistryKey, HKeyNameToWindowsName(name), includeSections) { @@ -198,7 +197,6 @@ internal TransactedRegistrySecurity(string name, AccessControlSections includeSe } */ - [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // Suppressed because the passed name and hkey won't change. [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")] internal TransactedRegistrySecurity(SafeRegistryHandle hKey, string name, AccessControlSections includeSections) @@ -234,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); @@ -250,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); @@ -278,7 +276,6 @@ internal AccessControlSections GetAccessControlSectionsFromChanges() return persistRules; } - [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)] // Suppressed because the passed keyName won't change. [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")] internal void Persist(SafeRegistryHandle hKey, string keyName) @@ -304,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) @@ -315,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) @@ -326,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) @@ -338,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) @@ -350,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) @@ -362,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) @@ -373,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) @@ -384,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) @@ -396,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) @@ -408,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) @@ -420,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) @@ -431,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); } @@ -440,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); } @@ -449,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/VariableProvider.cs b/src/System.Management.Automation/namespaces/VariableProvider.cs index 5da5351ebad..5f9934d326c 100644 --- a/src/System.Management.Automation/namespaces/VariableProvider.cs +++ b/src/System.Management.Automation/namespaces/VariableProvider.cs @@ -236,4 +236,3 @@ internal override bool CanRenameItem(object item) } } - diff --git a/src/System.Management.Automation/namespaces/Win32Native.cs b/src/System.Management.Automation/namespaces/Win32Native.cs index 41638e9b969..4f2c65c49f5 100644 --- a/src/System.Management.Automation/namespaces/Win32Native.cs +++ b/src/System.Management.Automation/namespaces/Win32Native.cs @@ -16,16 +16,10 @@ namespace Microsoft.PowerShell.Commands.Internal { using System; using System.Security; - using System.Text; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Management.Automation; using System.Diagnostics.CodeAnalysis; - using System.Runtime.ConstrainedExecution; - - using BOOL = System.Int32; - using DWORD = System.UInt32; - using ULONG = System.UInt32; /** * Win32 encapsulation for MSCORLIB. @@ -33,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 @@ -82,14 +76,14 @@ internal enum SID_NAME_USE #region Struct - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SID_AND_ATTRIBUTES { internal IntPtr Sid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_USER { internal SID_AND_ATTRIBUTES User; @@ -112,8 +106,6 @@ internal struct TOKEN_USER /// /// [DllImport(PinvokeDllNames.LookupAccountSidDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] private static extern unsafe bool LookupAccountSid(string lpSystemName, IntPtr sid, @@ -145,9 +137,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, } [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true)] - [ResourceExposure(ResourceScope.Machine)] - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr handle); @@ -159,8 +148,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, /// Process token. /// The current process token. [DllImport(PinvokeDllNames.OpenProcessTokenDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); @@ -175,8 +162,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, /// /// [DllImport(PinvokeDllNames.GetTokenInformationDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetTokenInformation(IntPtr tokenHandle, TOKEN_INFORMATION_CLASS tokenInformationClass, diff --git a/src/System.Management.Automation/resources/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..e869e98e742 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} ({2}). + 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 795a23a6327..2d8445deb4f 100644 --- a/src/System.Management.Automation/resources/DiscoveryExceptions.resx +++ b/src/System.Management.Automation/resources/DiscoveryExceptions.resx @@ -133,7 +133,7 @@ Cannot process the cmdlet. A cmdlet name must consist of a verb and noun pair separated by '-'. - The term '{0}' is not recognized as the name of a cmdlet, function, script file, or operable program. + The term '{0}' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. @@ -143,7 +143,7 @@ Check the spelling of the name, or if a path was included, verify that the path The argument '{0}' is not recognized as a cmdlet, possibly because it does not derive from the Cmdlet or PSCmdlet classes: {1} - Cannot resolve alias '{0}' because it refers to term '{1}', which is not recognized as a cmdlet, function, operable program, or script file. Verify the term and try again. + Cannot resolve alias '{0}' because it refers to term '{1}', which is not recognized as a cmdlet, function, executable program, or script file. Verify the term and try again. Parameter '{0}' with value '{1}' cannot be processed because it is not a cmdlet and cannot be processed by the CommandProcessor. @@ -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..ae797e8af12 100644 --- a/src/System.Management.Automation/resources/HelpErrors.resx +++ b/src/System.Management.Automation/resources/HelpErrors.resx @@ -179,7 +179,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator" command, and try running Update-Help again. - To use the {0}, install Windows PowerShell ISE by using Server Manager, and then restart this application. ({1}) + To use the {0}, make sure your application uses 'Microsoft.NET.Sdk.WindowsDesktop' as the project SDK and the corresponding assembly 'Microsoft.PowerShell.GraphicalHost' is available. ({1}) {0} does not work in a remote session. @@ -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..b8dc3ea4eb4 100644 --- a/src/System.Management.Automation/resources/Metadata.resx +++ b/src/System.Management.Automation/resources/Metadata.resx @@ -175,7 +175,7 @@ The character length ({1}) of the argument is too short. Specify an argument with a length that is greater than or equal to "{0}", and then try the command again. - The character length of the {1} argument is too long. Shorten the character length of the argument so it is fewer than or equal to "{0}" characters, and then try the command again. + The character length ({1}) of the argument is too long. Specify an argument with a length that is shorter than or equal to "{0}", and then try the command again. The argument "{0}" does not belong to the set "{1}" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again. @@ -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/PSConfigurationStrings.resx b/src/System.Management.Automation/resources/PSConfigurationStrings.resx new file mode 100644 index 00000000000..a1671d7f9e8 --- /dev/null +++ b/src/System.Management.Automation/resources/PSConfigurationStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + PowerShell has stopped working because of a security issue: Cannot read the configuration file: {0} + + 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 b0d84cc1136..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 @@ -1221,13 +1073,7 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Cannot perform call. - ResolveComReference.CannotRetrieveTypeInformation. - - - COM object does not support events. - - - COM object does not support specified source interface. + Cannot retrieve type information. Could not get dispatch ID for {0} (error: {1}). @@ -1253,24 +1099,15 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent IDispatch::GetIDsOfNames behaved unexpectedly for {0}. - - This method should not be called. - Marshal.SetComObjectData failed. Unexpected VarEnum {0}. - - Attempting to wrap an unsupported enum type. - Attempting to pass an event handler of an unsupported type. - - Variant.GetAccessor cannot handle {0}. - Configuration keyword is not supported in PowerShell 6+. @@ -1392,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 @@ -1467,30 +1307,12 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent Conflict in using PsDscRunAsCredential for Resource {0} because it already specifies PsDscRunAsCredential value. We can only use one PsDscRunAsCredential for the composite resource. + + Unable to find DSC schema store at "{0}". Please ensure PSDesiredStateConfiguration v3 module is installed. + {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. @@ -1509,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 16fb037a4d5..07a4677fa2f 100644 --- a/src/System.Management.Automation/resources/PathUtilsStrings.resx +++ b/src/System.Management.Automation/resources/PathUtilsStrings.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + Encoding 'UTF-7' is obsolete, please use UTF-8. + File {0} already exists and {1} was specified. @@ -135,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..b5905f1cf7a 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 7+. 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 or the named-pipe listener was disabled. 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,31 @@ 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. + + + Hyper-V {0} sent an invalid {1} response during the connection negotiation. + + + Negotiating a secure connection to Hyper-V failed. Make sure the Host and Guest are updated with all relevant Microsoft Updates. + diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx index c4a485121c7..985056b69ee 100644 --- a/src/System.Management.Automation/resources/RunspaceInit.resx +++ b/src/System.Management.Automation/resources/RunspaceInit.resx @@ -153,6 +153,12 @@ The text encoding used when piping text to a native executable file + + The text encoding used when reading output text from a native executable file + + + Configuration controlling how text is rendered. + Variable to contain the name of the email server. This can be used instead of the HostName parameter in the Send-MailMessage cmdlet. @@ -183,9 +189,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 new file mode 100644 index 00000000000..14ff340bed1 --- /dev/null +++ b/src/System.Management.Automation/resources/SubsystemStrings.resx @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 subsystem '{0}' does not allow more than one implementation to be registered. + + + The implementation with Id '{0}' was already registered for the subsystem '{1}'. + + + The subsystem '{0}' does not allow the unregistration of an implementation. + + + No implementation was registered for the subsystem '{0}'. + + + A registered implementation with the Id '{0}' was not found. + + + 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. + + + 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. + + + The 'Id' property of an implementation for the subsystem '{0}' cannot be an empty GUID. + + + The 'Name' property of an implementation for the subsystem '{0}' cannot be null or an empty string. + + + The 'Description' property of an implementation for the subsystem '{0}' cannot be null or an empty string. + + diff --git a/src/System.Management.Automation/resources/SuggestionStrings.resx b/src/System.Management.Automation/resources/SuggestionStrings.resx index cd44c33e759..39fbc3469f2 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -117,22 +117,13 @@ 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 most similar commands are: {0}. - - - Rule must be a ScriptBlock for dynamic match types. - - - MatchType must be 'Command', 'Error', or 'Dynamic'. + The most similar commands are: diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 33710a77176..298de92da3c 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 7+ to run. + + + Specifies that the script requires Windows PowerShell 5.1 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 cf5e559f830..3a7720616d1 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.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !timeStampServerUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { throw PSTraceSource.NewArgumentException( nameof(certificate), @@ -185,14 +192,14 @@ 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, IntPtr.Zero); #pragma warning restore 56523 - if (si.pSignExtInfo != null) + if (si.pSignExtInfo != IntPtr.Zero) { Marshal.DestroyStructure(si.pSignExtInfo); Marshal.FreeCoTaskMem(si.pSignExtInfo); @@ -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,91 +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 = - (NativeMethods.CRYPT_PROVIDER_CERT) 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); @@ -597,46 +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 = - (NativeMethods.CRYPT_PROVIDER_SGNR) - 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 050c373af74..4892f7434e4 100644 --- a/src/System.Management.Automation/security/CatalogHelper.cs +++ b/src/System.Management.Automation/security/CatalogHelper.cs @@ -1,22 +1,18 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #if !UNIX -using Dbg = System.Management.Automation; -using System; -using System.Text; using System.Security.Cryptography; using System.Collections.Generic; -using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Management.Automation.Internal; -using System.Management.Automation.Provider; using System.Management.Automation.Security; +using System.Management.Automation.Win32Native; using System.Runtime.InteropServices; -using DWORD = System.UInt32; namespace System.Management.Automation { @@ -73,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; /// @@ -88,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) { @@ -225,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; @@ -253,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) { @@ -277,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); } } @@ -342,7 +335,7 @@ internal static FileInfo GenerateCatalog(PSCmdlet cmdlet, Collection Pat { // Generate Path for Catalog Definition File string cdfFilePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetRandomFileName()); - cdfFilePath = cdfFilePath + ".cdf"; + cdfFilePath += ".cdf"; try { cdfFilePath = GenerateCDFFile(Path, catalogFilePath, cdfFilePath, catalogVersion, hashAlgorithm); @@ -379,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 @@ -405,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); - DWORD GENERIC_READ = 0x80000000; - 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; @@ -482,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; @@ -697,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(); @@ -790,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) @@ -820,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/CredentialParameter.cs b/src/System.Management.Automation/security/CredentialParameter.cs index 0d48e4fa738..1ea19691ae7 100644 --- a/src/System.Management.Automation/security/CredentialParameter.cs +++ b/src/System.Management.Automation/security/CredentialParameter.cs @@ -89,4 +89,3 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input } #pragma warning restore 56506 - diff --git a/src/System.Management.Automation/security/MshSignature.cs b/src/System.Management.Automation/security/MshSignature.cs index df20f7d0e63..7cbaf98d3a5 100644 --- a/src/System.Management.Automation/security/MshSignature.cs +++ b/src/System.Management.Automation/security/MshSignature.cs @@ -5,7 +5,6 @@ using System.Management.Automation.Internal; using System.Security.Cryptography.X509Certificates; -using Dbg = System.Management.Automation; using DWORD = System.UInt32; namespace System.Management.Automation @@ -72,7 +71,7 @@ public enum SignatureStatus /// with the current system. /// Incompatible - }; + } /// /// Defines the valid types of signatures. @@ -93,7 +92,7 @@ public enum SignatureType /// The signature is a catalog signature. /// Catalog = 2 - }; + } /// /// Represents a digital signature on a signed @@ -111,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. @@ -186,6 +185,11 @@ public string Path /// public bool IsOSBinary { get; internal set; } + /// + /// Gets the Subject Alternative Name from the signer certificate. + /// + public string[] SubjectAlternativeName { get; private set; } + /// /// Constructor for class Signature /// @@ -278,6 +282,9 @@ private void Init(string filePath, _statusMessage = GetSignatureStatusMessage(isc, error, filePath); + + // Extract Subject Alternative Name from the signer certificate + SubjectAlternativeName = GetSubjectAlternativeName(signer); } private static SignatureStatus GetSignatureStatusFromWin32Error(DWORD error) @@ -390,6 +397,34 @@ private static string GetSignatureStatusMessage(SignatureStatus status, return message; } - }; -} + /// + /// Extracts the Subject Alternative Name from the certificate. + /// + /// The certificate to extract SAN from. + /// Array of SAN entries or null if not found. + private static string[] GetSubjectAlternativeName(X509Certificate2 certificate) + { + if (certificate == null) + { + return null; + } + + foreach (X509Extension extension in certificate.Extensions) + { + if (extension.Oid != null && extension.Oid.Value == CertificateFilterInfo.SubjectAlternativeNameOid) + { + string formatted = extension.Format(multiLine: true); + if (string.IsNullOrEmpty(formatted)) + { + return null; + } + + return formatted.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); + } + } + + return null; + } + } +} diff --git a/src/System.Management.Automation/security/SecureStringHelper.cs b/src/System.Management.Automation/security/SecureStringHelper.cs index e6180dc39b7..5ff5881bab4 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; @@ -30,7 +31,7 @@ internal static class SecureStringHelper /// /// Input data. /// A SecureString . - private static SecureString New(byte[] data) + internal static SecureString New(byte[] data) { if ((data.Length % 2) != 0) { @@ -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(); @@ -600,7 +594,7 @@ internal static class CAPI internal const int E_FILENOTFOUND = unchecked((int)0x80070002); // File not found internal const int ERROR_FILE_NOT_FOUND = 2; // File not found - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPTOAPI_BLOB { internal uint cbData; @@ -617,23 +611,23 @@ internal static bool ErrorMayBeCausedByUnloadedProfile(int errorCode) [DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptProtectData( - [In] IntPtr pDataIn, - [In] string szDataDescr, - [In] IntPtr pOptionalEntropy, - [In] IntPtr pvReserved, - [In] IntPtr pPromptStruct, - [In] uint dwFlags, + [In] IntPtr pDataIn, + [In] string szDataDescr, + [In] IntPtr pOptionalEntropy, + [In] IntPtr pvReserved, + [In] IntPtr pPromptStruct, + [In] uint dwFlags, [In, Out] IntPtr pDataBlob); [DllImport("CRYPT32.dll", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CryptUnprotectData( - [In] IntPtr pDataIn, - [In] IntPtr ppszDataDescr, - [In] IntPtr pOptionalEntropy, - [In] IntPtr pvReserved, - [In] IntPtr pPromptStruct, - [In] uint dwFlags, + [In] IntPtr pDataIn, + [In] IntPtr ppszDataDescr, + [In] IntPtr pOptionalEntropy, + [In] IntPtr pvReserved, + [In] IntPtr pPromptStruct, + [In] uint dwFlags, [In, Out] IntPtr pDataBlob); [DllImport("ntdll.dll", EntryPoint = "RtlZeroMemory", SetLastError = true)] @@ -647,4 +641,3 @@ internal static extern bool CryptUnprotectData( #endif } - diff --git a/src/System.Management.Automation/security/SecurityManager.cs b/src/System.Management.Automation/security/SecurityManager.cs index f6ae0fdf1f3..137daecc5b4 100644 --- a/src/System.Management.Automation/security/SecurityManager.cs +++ b/src/System.Management.Automation/security/SecurityManager.cs @@ -11,6 +11,7 @@ using System.Management.Automation.Security; using System.Security; using System.Security.Cryptography.X509Certificates; +using System.Text; using Dbg = System.Management.Automation; @@ -18,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: /// @@ -39,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. @@ -68,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 @@ -139,10 +140,6 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r // Get the execution policy _executionPolicy = SecuritySupport.GetExecutionPolicy(_shellId); - // See if they want to bypass the authorization manager - if (_executionPolicy == ExecutionPolicy.Bypass) - return true; - // Always check the SAFER APIs if code integrity isn't being handled system-wide through // WLDP or AppLocker. In those cases, the scripts will be run in ConstrainedLanguage. // Otherwise, block. @@ -163,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); @@ -183,6 +183,13 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r } } + // WLDP and Applocker takes priority over powershell execution policy. + // See if they want to bypass the authorization manager + if (_executionPolicy == ExecutionPolicy.Bypass) + { + return true; + } + if (_executionPolicy == ExecutionPolicy.Unrestricted) { // Product binaries are always trusted @@ -324,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) { @@ -368,7 +376,7 @@ private bool CheckPolicy(ExternalScriptInfo script, PSHost host, out Exception r return policyCheckPassed; } - private bool SetPolicyFromAuthenticodePrompt(string path, PSHost host, ref Exception reason, Signature signature) + private static bool SetPolicyFromAuthenticodePrompt(string path, PSHost host, ref Exception reason, Signature signature) { bool policyCheckPassed = false; @@ -383,7 +391,9 @@ private bool SetPolicyFromAuthenticodePrompt(string path, PSHost host, ref Excep { TrustPublisher(signature); policyCheckPassed = true; - } break; + } + + break; case RunPromptDecision.DoNotRun: policyCheckPassed = false; reasonMessage = StringUtil.Format(Authenticode.Reason_DoNotRun, path); @@ -395,13 +405,15 @@ private bool SetPolicyFromAuthenticodePrompt(string path, PSHost host, ref Excep reasonMessage = StringUtil.Format(Authenticode.Reason_NeverRun, path); reason = new UnauthorizedAccessException(reasonMessage); policyCheckPassed = false; - } break; + } + + break; } return policyCheckPassed; } - private bool IsLocalFile(string filename) + private static bool IsLocalFile(string filename) { #if UNIX return true; @@ -421,7 +433,7 @@ private bool IsLocalFile(string filename) // Checks that a publisher is trusted by the system or is one of // the signed product binaries - private bool IsTrustedPublisher(Signature signature, string file) + private static bool IsTrustedPublisher(Signature signature, string file) { // Get the thumbprint of the current signature X509Certificate2 signerCertificate = signature.SignerCertificate; @@ -434,13 +446,18 @@ private 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; } - private bool IsUntrustedPublisher(Signature signature, string file) + private static bool IsUntrustedPublisher(Signature signature, string file) { // Get the thumbprint of the current signature X509Certificate2 signerCertificate = signature.SignerCertificate; @@ -463,7 +480,7 @@ private bool IsUntrustedPublisher(Signature signature, string file) /// Trust a publisher by adding it to the "Trusted Publishers" store. /// /// - private void TrustPublisher(Signature signature) + private static void TrustPublisher(Signature signature) { // Get the certificate of the signer X509Certificate2 signerCertificate = signature.SignerCertificate; @@ -481,7 +498,7 @@ private void TrustPublisher(Signature signature) } } - private void UntrustPublisher(Signature signature) + private static void UntrustPublisher(Signature signature) { // Get the certificate of the signer X509Certificate2 signerCertificate = signature.SignerCertificate; @@ -512,16 +529,29 @@ private void UntrustPublisher(Signature signature) } } - private Signature GetSignatureWithEncodingRetry(string path, ExternalScriptInfo script) + // Check the signature via the SIP which should never erroneously validate an invalid signature + // or altered script. + private static Signature GetSignatureWithEncodingRetry(string path, ExternalScriptInfo script) { - string verificationContents = System.Text.Encoding.Unicode.GetString(script.OriginalEncoding.GetPreamble()) + script.ScriptContents; - Signature signature = SignatureHelper.GetSignature(path, verificationContents); + // Invoke the SIP directly with the most simple method + Signature signature = SignatureHelper.GetSignature(path, fileContent: null); + if (signature.Status == SignatureStatus.Valid) + { + return signature; + } + // try harder to validate the signature by being explicit about encoding + // and providing the script contents + 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 != System.Text.Encoding.Unicode)) + if (signature.Status != SignatureStatus.Valid + && script.OriginalEncoding != Encoding.Unicode) { - verificationContents = System.Text.Encoding.Unicode.GetString(System.Text.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; @@ -530,6 +560,17 @@ private Signature GetSignatureWithEncodingRetry(string path, ExternalScriptInfo 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 /// @@ -610,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; @@ -636,7 +683,7 @@ protected internal override bool ShouldRun(CommandInfo commandInfo, return allowRun; } - private RunPromptDecision AuthenticodePrompt(string path, + private static RunPromptDecision AuthenticodePrompt(string path, Signature signature, PSHost host) { @@ -709,7 +756,7 @@ private RunPromptDecision AuthenticodePrompt(string path, return decision; } - private RunPromptDecision RemoteFilePrompt(string path, PSHost host) + private static RunPromptDecision RemoteFilePrompt(string path, PSHost host) { if ((host == null) || (host.UI == null)) { @@ -739,7 +786,7 @@ private RunPromptDecision RemoteFilePrompt(string path, PSHost host) } } - private Collection GetAuthenticodePromptChoices() + private static Collection GetAuthenticodePromptChoices() { Collection choices = new Collection(); @@ -760,7 +807,7 @@ private Collection GetAuthenticodePromptChoices() return choices; } - private Collection GetRemoteFilePromptChoices() + private static Collection GetRemoteFilePromptChoices() { Collection choices = new Collection(); @@ -779,4 +826,3 @@ private Collection GetRemoteFilePromptChoices() } } } - diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index 90d4a71d73b..dc6d048c5b1 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, @@ -63,7 +63,7 @@ public enum ExecutionPolicy /// Default - The most restrictive policy available. /// Default = Restricted - }; + } /// /// Defines the available configuration scopes for an execution @@ -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(); @@ -704,7 +701,6 @@ internal static Collection GetCertEKU(X509Certificate2 cert) out structSize)) { Security.NativeMethods.CERT_ENHKEY_USAGE ekuStruct = - (Security.NativeMethods.CERT_ENHKEY_USAGE) Marshal.PtrToStructure(ekuBuffer); IntPtr ep = ekuStruct.rgpszUsageIdentifier; IntPtr ekuptr; @@ -819,6 +815,7 @@ internal DateTime Expiring // The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need // new OIDs, we can assign them under this branch. internal const string DocumentEncryptionOid = "1.3.6.1.4.1.311.80.1"; + internal const string SubjectAlternativeNameOid = "2.5.29.17"; } } @@ -857,6 +854,7 @@ internal enum CertificatePurpose namespace System.Management.Automation { + using System.Management.Automation.Tracing; using System.Security.Cryptography.Pkcs; /// @@ -996,7 +994,7 @@ public CmsMessageRecipient(string identifier) this.Certificates = new X509Certificate2Collection(); } - private string _identifier = null; + private readonly string _identifier; /// /// Creates an instance of the CmsMessageRecipient class. @@ -1008,7 +1006,7 @@ public CmsMessageRecipient(X509Certificate2 certificate) this.Certificates = new X509Certificate2Collection(); } - private X509Certificate2 _pendingCertificate = null; + private readonly X509Certificate2 _pendingCertificate; /// /// Gets the certificate associated with this recipient. @@ -1107,7 +1105,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) @@ -1185,7 +1186,9 @@ private void ResolveFromPath(SessionState sessionState, ResolutionPurpose purpos try { + #pragma warning disable SYSLIB0057 certificate = new X509Certificate2(path); + #pragma warning restore SYSLIB0057 } catch (Exception) { @@ -1214,7 +1217,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)) { @@ -1335,11 +1338,24 @@ public enum ResolutionPurpose Decryption } - internal class AmsiUtils + 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() @@ -1348,34 +1364,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; } } @@ -1397,7 +1400,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)) { @@ -1407,7 +1413,7 @@ internal static AmsiNativeMethods.AMSI_RESULT WinScanContent(string content, str const string EICAR_STRING = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; if (InternalTestHooks.UseDebugAmsiImplementation) { - if (content.IndexOf(EICAR_STRING, StringComparison.Ordinal) >= 0) + if (content.Contains(EICAR_STRING, StringComparison.Ordinal)) { return AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED; } @@ -1416,6 +1422,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; } @@ -1423,38 +1430,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) @@ -1467,6 +1451,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) @@ -1485,6 +1470,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; } @@ -1492,12 +1478,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) @@ -1506,14 +1607,13 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) } } - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiContext = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiSession = IntPtr.Zero; - private static 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) @@ -1581,7 +1681,7 @@ internal static void WinUninitialize() public static bool AmsiInitialized = false; public static bool AmsiCleanedUp = false; - internal class AmsiNativeMethods + internal static class AmsiNativeMethods { internal enum AMSI_RESULT { @@ -1602,29 +1702,29 @@ internal enum AMSI_RESULT /// Return Type: HRESULT->LONG->int ///appName: LPCWSTR->WCHAR* ///amsiContext: HAMSICONTEXT* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiInitialize( - [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string appName, ref System.IntPtr amsiContext); + [In][MarshalAs(UnmanagedType.LPWStr)] string appName, ref System.IntPtr amsiContext); /// Return Type: void ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)] internal static extern void AmsiUninitialize(System.IntPtr amsiContext); /// Return Type: HRESULT->LONG->int ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* ///amsiSession: HAMSISESSION* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiOpenSession(System.IntPtr amsiContext, ref System.IntPtr amsiSession); /// Return Type: void ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* ///amsiSession: HAMSISESSION->HAMSISESSION__* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)] internal static extern void AmsiCloseSession(System.IntPtr amsiContext, System.IntPtr amsiSession); /// Return Type: HRESULT->LONG->int @@ -1634,11 +1734,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__* @@ -1646,11 +1765,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 7cda5b88260..6dd8f59d613 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -7,14 +7,13 @@ using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; using System.Management.Automation.Internal; -using System.Runtime.ConstrainedExecution; using DWORD = System.UInt32; using BOOL = System.UInt32; namespace System.Management.Automation.Security { // Crypto API native constants - internal partial class NativeConstants + internal static partial class NativeConstants { internal const int CRYPT_OID_INFO_OID_KEY = 1; internal const int CRYPT_OID_INFO_NAME_KEY = 2; @@ -194,7 +193,6 @@ internal enum CertFindType CERT_FIND_HASH_STR = 20 << 16, // thumbprint } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool CertCloseStore(IntPtr hCertStore, int dwFlags); @@ -472,7 +470,6 @@ bool CertAddCertificateContextToStore(IntPtr hCertStore, DWORD dwAddDisposition, ref IntPtr ppStoreContext); - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] [DllImport("crypt32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool CertFreeCertificateContext(IntPtr certContext); @@ -559,7 +556,7 @@ internal enum CryptUIFlags { CRYPTUI_WIZ_NO_UI = 0x0001 // other flags not used - }; + } [StructLayout(LayoutKind.Sequential)] internal struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO @@ -578,14 +575,14 @@ internal struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO internal DWORD dwAdditionalCertChoice; internal IntPtr pSignExtInfo; // PCCRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO - }; + } [Flags] internal enum SignInfoSubjectChoice { CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE = 0x01 // CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_BLOB = 0x02 NotUsed - }; + } [Flags] internal enum SignInfoCertChoice @@ -593,14 +590,14 @@ internal enum SignInfoCertChoice CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 0x01 // CRYPTUI_WIZ_DIGITAL_SIGN_STORE = 0x02, NotUsed // CRYPTUI_WIZ_DIGITAL_SIGN_PVK = 0x03, NotUsed - }; + } [Flags] internal enum SignInfoAdditionalCertChoice { CRYPTUI_WIZ_DIGITAL_SIGN_ADD_CHAIN = 1, CRYPTUI_WIZ_DIGITAL_SIGN_ADD_CHAIN_NO_ROOT = 2 - }; + } [StructLayout(LayoutKind.Sequential)] internal struct CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO @@ -621,9 +618,8 @@ internal struct CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO internal IntPtr hAdditionalCertStoreNotUsed; // HCERTSTORE internal IntPtr psAuthenticatedNotUsed; // PCRYPT_ATTRIBUTES internal IntPtr psUnauthenticatedNotUsed; // PCRYPT_ATTRIBUTES - }; + } - [ArchitectureSensitive] internal static CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO InitSignInfoExtendedStruct(string description, string moreInfoUrl, @@ -657,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 @@ -727,7 +723,6 @@ internal static extern IntPtr CryptFindOIDInfo( System.IntPtr pvKey, uint dwGroupId); - [ArchitectureSensitive] internal static DWORD GetCertChoiceFromSigningOption( SigningOption option) { @@ -755,7 +750,6 @@ internal static DWORD GetCertChoiceFromSigningOption( return cc; } - [ArchitectureSensitive] internal static CRYPTUI_WIZ_DIGITAL_SIGN_INFO InitSignInfoStruct(string fileName, X509Certificate2 signingCert, @@ -782,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 } // @@ -1213,7 +824,7 @@ internal struct CERT_ENHKEY_USAGE // [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.LPStr, SizeParamIndex=0)] // internal string[] rgpszUsageIdentifier; // LPSTR* internal IntPtr rgpszUsageIdentifier; - }; + } internal enum SIGNATURE_STATE { @@ -1287,7 +898,7 @@ internal enum SIGNATURE_INFO_TYPE SIT_CATALOG, } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct SIGNATURE_INFO { /// DWORD->unsigned int @@ -1306,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 @@ -1336,7 +947,7 @@ internal struct SIGNATURE_INFO internal int fOSBinary; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_INFO { /// DWORD->unsigned int @@ -1376,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 @@ -1397,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 @@ -1407,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 @@ -1420,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 @@ -1475,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 @@ -1492,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, @@ -1505,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 @@ -1526,7 +1137,7 @@ internal struct SAFER_CODE_PROPERTIES public uint dwCheckFlags; /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] public string ImagePath; /// HANDLE->void* @@ -1536,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)] @@ -1561,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 @@ -1594,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 @@ -1676,28 +1287,28 @@ internal enum SecurityInformation : uint UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACL { internal byte AclRevision; @@ -1707,7 +1318,7 @@ internal struct ACL internal ushort Sbz2; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACE_HEADER { internal byte AceType; @@ -1715,7 +1326,7 @@ internal struct ACE_HEADER internal ushort AceSize; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_AUDIT_ACE { internal ACE_HEADER Header; @@ -1723,7 +1334,7 @@ internal struct SYSTEM_AUDIT_ACE internal uint SidStart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LSA_UNICODE_STRING { internal ushort Length; @@ -1731,7 +1342,7 @@ internal struct LSA_UNICODE_STRING internal IntPtr Buffer; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CENTRAL_ACCESS_POLICY { internal IntPtr CAPID; @@ -1856,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 (IntPtr.Zero != module) - { - FreeLibrary(module); - DllExists = true; - } - } - catch (Exception) - { - } - - return DllExists; - } } // Constants needed for Catalog Error Handling @@ -1933,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 struct CRYPTCATCDF - { - private DWORD _cbStruct; - private IntPtr _hFile; - private DWORD _dwCurFilePos; - private DWORD _dwLastMemberOffset; - private BOOL _fEOF; - - [MarshalAs(UnmanagedType.LPWStr)] - private string _pwszResultDir; - - private 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 608503b932f..ab49f927614 100644 --- a/src/System.Management.Automation/security/wldpNativeMethods.cs +++ b/src/System.Management.Automation/security/wldpNativeMethods.cs @@ -6,13 +6,46 @@ // #if !UNIX +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; -using Microsoft.Win32; +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. /// @@ -42,29 +75,188 @@ 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. /// /// An EnforcementMode that describes the system policy. public static SystemEnforcementMode GetSystemLockdownPolicy() { - if (s_allowDebugOverridePolicy || (s_systemLockdownPolicy == null)) + if (s_systemLockdownPolicy == null) { lock (s_systemLockdownPolicyLock) { - if (s_allowDebugOverridePolicy || (s_systemLockdownPolicy == null)) - { - s_systemLockdownPolicy = GetLockdownPolicy(null, null); - } + s_systemLockdownPolicy ??= GetLockdownPolicy(path: null, handle: null); + } + } + else if (s_allowDebugOverridePolicy) + { + lock (s_systemLockdownPolicyLock) + { + 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. @@ -72,40 +264,65 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() /// An EnforcementMode that describes policy. public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle) { - // Check the WLDP API - SystemEnforcementMode lockdownPolicy = GetWldpPolicy(path, handle); - if (lockdownPolicy == SystemEnforcementMode.Enforce) + 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 + if (wldpFilePolicy is SystemScriptFileEnforcement.Block or SystemScriptFileEnforcement.AllowConstrained) { - return lockdownPolicy; + return wldpFilePolicy; } - // 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.Enforce) || - (systemWldpPolicy == SystemEnforcementMode.Audit)) + // Check the AppLocker File policy via API + // This needs to be checked before WLDP audit policy + // So, that we don't end up in Audit mode, + // when we should be enforce mode. + var appLockerFilePolicy = GetAppLockerPolicy(path, handle); + if (appLockerFilePolicy == SystemEnforcementMode.Enforce) { - return lockdownPolicy; + return ConvertToModernFileEnforcement(appLockerFilePolicy); } - // Check the AppLocker API - lockdownPolicy = GetAppLockerPolicy(path, handle); - if (lockdownPolicy == SystemEnforcementMode.Enforce) + // 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. + if (s_cachedWldpSystemPolicy is SystemEnforcementMode.Audit or SystemEnforcementMode.Enforce + || wldpFilePolicy is SystemScriptFileEnforcement.AllowConstrainedAudit) { - return lockdownPolicy; + 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 lockdownPolicy; + 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", @@ -147,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); @@ -165,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); } } @@ -224,27 +443,42 @@ private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle // AppLocker fails when you try to check a policy on a file // with no content. So create a scratch file and test on that. - string dtAppLockerTestFileContents = AppLockerTestFileContents + DateTime.Now; + string dtAppLockerTestFileContents = AppLockerTestFileContents + Environment.TickCount64; 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 @@ -343,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; @@ -352,39 +586,21 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) { // Assume everything under SYSTEM32 is trusted, with a purposefully sloppy // check so that we can actually put it in the filename during testing. - if (path.IndexOf("System32", StringComparison.OrdinalIgnoreCase) >= 0) + if (path.Contains("System32", StringComparison.OrdinalIgnoreCase)) { + modernEnforcement = SystemScriptFileEnforcement.Allow; return SystemEnforcementMode.None; } - using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default)) + // No explicit debug allowance for the file, so return the system policy if there is one. + modernEnforcement = s_systemLockdownPolicy switch { - using (RegistryKey wldpPolicy = hklm.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\CI\\TRSData")) - { - if (wldpPolicy != null) - { - object exclusionPathsKey = wldpPolicy.GetValue("TestPath"); - - wldpPolicy.Close(); - hklm.Close(); + SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained, + SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit, + SystemEnforcementMode.None => SystemScriptFileEnforcement.None, + _ => SystemScriptFileEnforcement.None, + }; - if (exclusionPathsKey != null) - { - string[] exclusionPaths = (string[])exclusionPathsKey; - foreach (string exclusionPath in exclusionPaths) - { - if (path.IndexOf(exclusionPath, StringComparison.OrdinalIgnoreCase) >= 0) - { - return SystemEnforcementMode.None; - } - } - } - } - } - } - - // No explicit debug allowance for the file, so return the system policy if there - // is one. return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None); } @@ -394,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; } @@ -409,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(); @@ -509,7 +736,7 @@ internal static string DumpLockdownState(uint pdwLockdownState) /// /// Native constants for dealing with the lockdown policy. /// - internal class WldpNativeConstants + internal static class WldpNativeConstants { internal const uint WLDP_HOST_INFORMATION_REVISION = 0x00000001; @@ -556,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 class WldpNativeMethods + 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* @@ -576,7 +852,11 @@ internal 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 9e8c2585215..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 { /// @@ -78,7 +77,7 @@ private void CreateErrorRecord() { foreach (PSSnapInException e in PSSnapInExceptions) { - sb.Append("\n"); + sb.Append('\n'); sb.Append(e.Message); } } @@ -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 { @@ -115,4 +114,3 @@ public override string Message } } } - diff --git a/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs b/src/System.Management.Automation/singleshell/config/MshSnapinInfo.cs index 33ef4a0cb9d..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,11 +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 9ee9588be65..71c81611440 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,36 +171,13 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 0638891eaa2..058c33a68bf 100644 --- a/src/System.Management.Automation/utils/ClrFacade.cs +++ b/src/System.Management.Automation/utils/ClrFacade.cs @@ -2,24 +2,15 @@ // Licensed under the MIT License. using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; using System.Management.Automation.Internal; using System.Management.Automation.Language; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; using System.Runtime.Loader; using System.Security; using System.Text; using System.Text.RegularExpressions; -using System.Threading; - -using Microsoft.Win32.SafeHandles; namespace System.Management.Automation { @@ -31,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); } } @@ -108,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. @@ -133,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 } @@ -148,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 @@ -242,7 +216,7 @@ private static SecurityZone MapSecurityZone(string filePath) // has 'dot' in it, the file will be treated as in Internet security zone. Otherwise, it's // in Intranet security zone. string hostName = uri.Host; - return hostName.Contains('.') ? SecurityZone.Intranet : SecurityZone.Internet; + return hostName.Contains('.') ? SecurityZone.Internet : SecurityZone.Intranet; } string root = Path.GetPathRoot(filePath); @@ -271,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; @@ -296,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; @@ -357,14 +337,14 @@ internal static string ToDmtfDateTime(DateTime date) string dmtfDateTime = date.Year.ToString(frmInt32).PadLeft(4, '0'); - dmtfDateTime = (dmtfDateTime + date.Month.ToString(frmInt32).PadLeft(2, '0')); - dmtfDateTime = (dmtfDateTime + date.Day.ToString(frmInt32).PadLeft(2, '0')); - dmtfDateTime = (dmtfDateTime + date.Hour.ToString(frmInt32).PadLeft(2, '0')); - dmtfDateTime = (dmtfDateTime + date.Minute.ToString(frmInt32).PadLeft(2, '0')); - dmtfDateTime = (dmtfDateTime + date.Second.ToString(frmInt32).PadLeft(2, '0')); - dmtfDateTime = (dmtfDateTime + "."); + dmtfDateTime += date.Month.ToString(frmInt32).PadLeft(2, '0'); + dmtfDateTime += date.Day.ToString(frmInt32).PadLeft(2, '0'); + dmtfDateTime += date.Hour.ToString(frmInt32).PadLeft(2, '0'); + dmtfDateTime += date.Minute.ToString(frmInt32).PadLeft(2, '0'); + 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; @@ -376,9 +356,9 @@ internal static string ToDmtfDateTime(DateTime date) strMicrosec = strMicrosec.Substring(0, 6); } - dmtfDateTime = dmtfDateTime + strMicrosec.PadLeft(6, '0'); + dmtfDateTime += strMicrosec.PadLeft(6, '0'); // adding the UTC offset - dmtfDateTime = dmtfDateTime + UtcString; + dmtfDateTime += UtcString; return dmtfDateTime; #else @@ -387,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 c7dc2cb2587..89580932359 100644 --- a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs +++ b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs @@ -4,7 +4,6 @@ using System.Collections.ObjectModel; using System.Management.Automation.Internal; using System.Runtime.Serialization; -using System.Security.Permissions; using System.Text; namespace System.Management.Automation @@ -12,7 +11,6 @@ namespace System.Management.Automation /// /// This exception is thrown when a command cannot be found. /// - [Serializable] public class CommandNotFoundException : RuntimeException { /// @@ -71,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. /// @@ -81,40 +78,13 @@ 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"); + throw new NotSupportedException(); } - /// - /// Serializes the CommandNotFoundException. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); - } - - base.GetObjectData(info, context); - info.AddValue("CommandName", _commandName); - } - #endregion Serialization - #region Properties /// /// Gets the ErrorRecord information for this exception. @@ -123,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; } @@ -153,8 +120,8 @@ public string CommandName #endregion Properties #region Private - private string _errorId = "CommandNotFoundException"; - private ErrorCategory _errorCategory = ErrorCategory.ObjectNotFound; + private readonly string _errorId = "CommandNotFoundException"; + private readonly ErrorCategory _errorCategory = ErrorCategory.ObjectNotFound; private static string BuildMessage( string commandName, @@ -163,7 +130,7 @@ params object[] messageArgs ) { object[] a; - if (messageArgs != null && 0 < messageArgs.Length) + if (messageArgs != null && messageArgs.Length > 0) { a = new object[messageArgs.Length + 1]; a[0] = commandName; @@ -183,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 { /// @@ -370,40 +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"); + throw new NotSupportedException(); } - /// - /// Gets the serialized data for the exception. - /// - /// - /// serialization information - /// - /// - /// streaming context - /// - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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); - } #endregion Serialization #region Properties @@ -416,7 +355,7 @@ public string CommandName get { return _commandName; } } - private string _commandName = string.Empty; + private readonly string _commandName = string.Empty; /// /// Gets the PSVersion that the script requires. @@ -426,7 +365,7 @@ public Version RequiresPSVersion get { return _requiresPSVersion; } } - private Version _requiresPSVersion; + private readonly Version _requiresPSVersion; /// /// Gets the missing snap-ins that the script requires. @@ -436,7 +375,7 @@ public ReadOnlyCollection MissingPSSnapIns get { return _missingPSSnapIns; } } - private ReadOnlyCollection _missingPSSnapIns = new ReadOnlyCollection(Array.Empty()); + private readonly ReadOnlyCollection _missingPSSnapIns = new ReadOnlyCollection(Array.Empty()); /// /// Gets or sets the ID of the shell. @@ -446,7 +385,7 @@ public string RequiresShellId get { return _requiresShellId; } } - private string _requiresShellId; + private readonly string _requiresShellId; /// /// Gets or sets the path to the incompatible shell. @@ -456,7 +395,7 @@ public string RequiresShellPath get { return _requiresShellPath; } } - private string _requiresShellPath; + private readonly string _requiresShellPath; #endregion Properties @@ -536,4 +475,3 @@ private static string BuildMessage(string commandName) #endregion Private } } - diff --git a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs index 4aebe05af59..668c95a25e6 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 f860bdd4b15..c82c96ecdc3 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Management.Automation.Remoting; -using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.Cryptography; @@ -43,7 +42,7 @@ internal static class PSCryptoNativeConverter /// public const uint PUBLICKEYBLOB = 0x00000006; - /// + /// /// PUBLICKEYBLOB header length. /// public const int PUBLICKEYBLOB_HEADER_LEN = 20; @@ -53,7 +52,7 @@ internal static class PSCryptoNativeConverter /// public const uint SIMPLEBLOB = 0x00000001; - /// + /// /// SIMPLEBLOB header length. /// public const int SIMPLEBLOB_HEADER_LEN = 12; @@ -64,7 +63,7 @@ internal static class PSCryptoNativeConverter private static int ToInt32LE(byte[] bytes, int offset) { - return (bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1 ] << 8) | bytes[offset]; + return (bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset]; } private static uint ToUInt32LE(byte[] bytes, int offset) @@ -74,7 +73,7 @@ private static uint ToUInt32LE(byte[] bytes, int offset) private static byte[] GetBytesLE(int val) { - return new [] { + return new[] { (byte)(val & 0xff), (byte)((val >> 8) & 0xff), (byte)((val >> 16) & 0xff), @@ -97,10 +96,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 +119,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) { @@ -140,7 +133,7 @@ private static RSAParameters GetParametersFromCapiPublicKeyBlob(byte[] blob, int try { - if ((blob[offset] != PUBLICKEYBLOB) || // PUBLICKEYBLOB (0x06) + if ((blob[offset] != PUBLICKEYBLOB) || // PUBLICKEYBLOB (0x06) (blob[offset + 1] != CUR_BLOB_VERSION) || // Version (0x02) (blob[offset + 2] != 0x00) || // Reserved (word) (blob[offset + 3] != 0x00) || @@ -175,10 +168,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 +211,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 +224,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 +234,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,12 +257,11 @@ 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 - private uint _errorCode; + private readonly uint _errorCode; #endregion Private Members @@ -302,7 +285,8 @@ internal uint ErrorCode /// /// Default constructor. /// - public PSCryptoException() : this(0, new StringBuilder(string.Empty)) { } + public PSCryptoException() + : this(0, new StringBuilder(string.Empty)) { } /// /// Constructor that will be used from within CryptoUtils. @@ -320,7 +304,8 @@ public PSCryptoException(uint errorCode, StringBuilder message) /// Constructor with just message but no inner exception. /// /// Error message associated with this failure. - public PSCryptoException(string message) : this(message, null) { } + public PSCryptoException(string message) + : this(message, null) { } /// /// Constructor with inner exception. @@ -329,8 +314,8 @@ public PSCryptoException(string message) : this(message, null) { } /// Inner exception. /// This constructor is currently not called /// explicitly from crypto utils - public PSCryptoException(string message, Exception innerException) : - base(message, innerException) + public PSCryptoException(string message, Exception innerException) + : base(message, innerException) { _errorCode = unchecked((uint)-1); } @@ -342,34 +327,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 @@ -379,14 +350,14 @@ 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; // bool indicating if session key was generated before private bool _sessionKeyGenerated = false; - private static object s_syncObject = new object(); + private static readonly object s_syncObject = new object(); #endregion Private Members @@ -437,7 +408,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; @@ -462,6 +433,7 @@ internal string SafeExportSessionKey() GenerateSessionKey(); // encrypt it + // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason. byte[] encryptedKey = _rsa.Encrypt(_aes.Key, RSAEncryptionPadding.Pkcs1); // convert the key to capi simpleblob format before exporting @@ -495,6 +467,7 @@ internal void ImportSessionKeyFromBase64EncodedString(string sessionKey) byte[] sessionKeyBlob = Convert.FromBase64String(sessionKey); byte[] rsaEncryptedKey = PSCryptoNativeConverter.FromCapiSimpleKeyBlob(sessionKeyBlob); + // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason. _aes.Key = _rsa.Decrypt(rsaEncryptedKey, RSAEncryptionPadding.Pkcs1); // now we have imported the key and will be able to @@ -603,41 +576,12 @@ internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer( #region IDisposable /// - /// Dispose resources. + /// Release all resources. /// public void Dispose() { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - // [SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)] - protected void Dispose(bool disposing) - { - if (disposing) - { - if (_rsa != null) - { - _rsa.Dispose(); - } - - if (_aes != null) - { - _aes.Dispose(); - } - } - } - - /// - /// Destructor. - /// - ~PSRSACryptoServiceProvider() - { - // When Dispose() is called, GC.SuppressFinalize() - // is called and therefore this finalizer will not - // be invoked. Hence this is run only on process - // shutdown - Dispose(true); + _rsa?.Dispose(); + _aes?.Dispose(); } #endregion IDisposable @@ -647,7 +591,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 @@ -657,7 +601,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 @@ -706,6 +650,78 @@ protected void RunKeyExchangeIfRequired() } } + /// + /// Gets the bytes of a secure string. + /// + private static byte[] GetBytesFromSecureString(SecureString secureString) + { + return secureString is null + ? null + : Microsoft.PowerShell.SecureStringHelper.GetData(secureString); + } + + /// + /// Gets a secure string from the specified byte array. + /// + private static SecureString GetSecureStringFromBytes(byte[] data) + { + Dbg.Assert(data is not null, "The passed-in data cannot be null."); + + try + { + return Microsoft.PowerShell.SecureStringHelper.New(data); + } + finally + { + // zero out the contents + Array.Clear(data); + } + } + + /// + /// Convert a secure string to a base64 encoded string. + /// + protected string ConvertSecureStringToBase64String(SecureString secureString) + { + string dataAsString = null; + byte[] data = GetBytesFromSecureString(secureString); + + if (data is not null) + { + try + { + dataAsString = Convert.ToBase64String(data); + } + finally + { + Array.Clear(data); + } + } + + return dataAsString; + } + + /// + /// Convert a base64 encoded string to a secure string. + /// + /// + /// + protected SecureString ConvertBase64StringToSecureString(string base64String) + { + try + { + byte[] data = Convert.FromBase64String(base64String); + return GetSecureStringFromBytes(data); + } + catch (FormatException) + { + // do nothing + // this catch is to ensure that the exception doesn't + // go unhandled leading to a crash + throw new PSCryptoException(); + } + } + /// /// Core logic to encrypt a string. Assumes session key is already generated. /// @@ -719,18 +735,10 @@ protected string EncryptSecureStringCore(SecureString secureString) if (_rsaCryptoProvider.CanEncrypt) { - IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(secureString); + byte[] data = GetBytesFromSecureString(secureString); - if (ptr != IntPtr.Zero) + if (data is not null) { - byte[] data = new byte[secureString.Length * 2]; - for (int i = 0; i < data.Length; i++) - { - data[i] = Marshal.ReadByte(ptr, i); - } - - Marshal.ZeroFreeCoTaskMemUnicode(ptr); - try { byte[] encryptedData = _rsaCryptoProvider.EncryptWithSessionKey(data); @@ -738,10 +746,7 @@ protected string EncryptSecureStringCore(SecureString secureString) } finally { - for (int j = 0; j < data.Length; j++) - { - data[j] = 0; - } + Array.Clear(data); } } } @@ -771,10 +776,11 @@ protected SecureString DecryptSecureStringCore(string encryptedString) // happened successfully if (_rsaCryptoProvider.CanEncrypt) { - byte[] data = null; try { - data = Convert.FromBase64String(encryptedString); + byte[] data = Convert.FromBase64String(encryptedString); + byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data); + secureString = GetSecureStringFromBytes(decryptedData); } catch (FormatException) { @@ -783,36 +789,6 @@ protected SecureString DecryptSecureStringCore(string encryptedString) // go unhandled leading to a crash throw new PSCryptoException(); } - - if (data != null) - { - byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data); - - secureString = new SecureString(); - UInt16 value = 0; - try - { - for (int i = 0; i < decryptedData.Length; i += 2) - { - value = (UInt16)(decryptedData[i] + (UInt16)(decryptedData[i + 1] << 8)); - secureString.AppendChar((char)value); - value = 0; - } - } - finally - { - // if there was an exception for whatever reason, - // clear the last value store in Value - value = 0; - - // zero out the contents - for (int i = 0; i < decryptedData.Length; i += 2) - { - decryptedData[i] = 0; - decryptedData[i + 1] = 0; - } - } - } } else { @@ -863,11 +839,7 @@ public void Dispose(bool disposing) { if (disposing) { - if (_rsaCryptoProvider != null) - { - _rsaCryptoProvider.Dispose(); - } - + _rsaCryptoProvider?.Dispose(); _rsaCryptoProvider = null; _keyExchangeCompleted.Dispose(); @@ -918,17 +890,30 @@ 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)) + bool initiateKeyExchange = true; + + if (Session is ServerRemoteSession session) { - _rsaCryptoProvider.GenerateSessionKey(); + Version clientProtocolVersion = session.Context.ClientCapability.ProtocolVersion; + if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertSecureStringToBase64String(secureString); + } + + if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_2) + { + // For client v2.2+, server will never initiate key exchange. + // For server, just the session key is required to encrypt/decrypt anything + initiateKeyExchange = false; + _rsaCryptoProvider.GenerateSessionKey(); + } } - else // older clients + + if (initiateKeyExchange) { + // older clients. RunKeyExchangeIfRequired(); } @@ -937,6 +922,12 @@ internal override string EncryptSecureString(SecureString secureString) internal override SecureString DecryptSecureString(string encryptedString) { + if (Session is ServerRemoteSession session && session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertBase64StringToSecureString(encryptedString); + } + RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); @@ -1048,6 +1039,12 @@ internal PSRemotingCryptoHelperClient() internal override string EncryptSecureString(SecureString secureString) { + if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertSecureStringToBase64String(secureString); + } + RunKeyExchangeIfRequired(); return EncryptSecureStringCore(secureString); @@ -1055,6 +1052,12 @@ internal override string EncryptSecureString(SecureString secureString) internal override SecureString DecryptSecureString(string encryptedString) { + if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertBase64StringToSecureString(encryptedString); + } + RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); diff --git a/src/System.Management.Automation/utils/EncodingUtils.cs b/src/System.Management.Automation/utils/EncodingUtils.cs index 8206b5cf8a3..cdb467d213a 100644 --- a/src/System.Management.Automation/utils/EncodingUtils.cs +++ b/src/System.Management.Automation/utils/EncodingUtils.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Text; using System.Management.Automation.Internal; @@ -12,39 +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 }, - { Utf7, System.Text.Encoding.UTF7 }, - { 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 }, + { String, Encoding.Unicode }, + { Unicode, Encoding.Unicode }, + { Unknown, Encoding.Unicode }, +#pragma warning disable SYSLIB0001 + { Utf7, Encoding.UTF7 }, +#pragma warning restore SYSLIB0001 + { Utf8, Encoding.Default }, + { Utf8Bom, Encoding.UTF8 }, + { Utf8NoBom, Encoding.Default }, + { Utf32, Encoding.UTF32 }, }; /// @@ -57,12 +60,17 @@ 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)) + { + cmdlet.WriteWarning(PathUtilsStrings.Utf7EncodingObsolete); + } + return foundEncoding; } @@ -81,6 +89,21 @@ internal static Encoding Convert(Cmdlet cmdlet, string encoding) return null; } + + /// + /// Warn if the encoding has been designated as obsolete. + /// + /// A cmdlet instance which is used to emit the warning. + /// The encoding to check for obsolescence. + internal static void WarnIfObsolete(Cmdlet cmdlet, Encoding encoding) + { + // Check for UTF-7 by checking for code page 65000 + // 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); + } + } } /// @@ -92,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: @@ -101,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; @@ -117,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 2b7fcd7cff4..ff41e045bbb 100644 --- a/src/System.Management.Automation/utils/ExecutionExceptions.cs +++ b/src/System.Management.Automation/utils/ExecutionExceptions.cs @@ -7,7 +7,6 @@ using System.Runtime.Serialization; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; -using System.Security.Permissions; #pragma warning disable 1634, 1691 // Stops compiler from warning about unknown warnings @@ -20,7 +19,6 @@ namespace System.Management.Automation /// /// InnerException is the error which the cmdlet hit. /// - [Serializable] public class CmdletInvocationException : RuntimeException { #region ctor @@ -31,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) @@ -56,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); } @@ -123,33 +114,11 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -163,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; } @@ -189,7 +155,6 @@ 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 @@ -206,10 +171,7 @@ internal CmdletProviderInvocationException( InvocationInfo myInvocation) : base(GetInnerException(innerException), myInvocation) { - if (innerException == null) - { - throw new ArgumentNullException(nameof(innerException)); - } + ArgumentNullException.ThrowIfNull(innerException); _providerInvocationException = innerException; } @@ -231,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(); } /// @@ -276,7 +238,7 @@ public ProviderInvocationException ProviderInvocationException } [NonSerialized] - private ProviderInvocationException _providerInvocationException; + private readonly ProviderInvocationException _providerInvocationException; /// /// This is the ProviderInfo associated with the provider which @@ -287,9 +249,7 @@ public ProviderInfo ProviderInfo { get { - return (_providerInvocationException == null) - ? null - : _providerInvocationException.ProviderInfo; + return _providerInvocationException?.ProviderInfo; } } @@ -298,7 +258,7 @@ public ProviderInfo ProviderInfo #region Internal private static Exception GetInnerException(Exception e) { - return (e == null) ? null : e.InnerException; + return e?.InnerException; } #endregion Internal } @@ -314,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. + /// PowerShell Engine's call to ProcessRecord, the PowerShell Engine will handle it properly. /// - [Serializable] public class PipelineStoppedException : RuntimeException { #region ctor @@ -345,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(); } /// @@ -385,7 +343,6 @@ public PipelineStoppedException(string message, /// been stopped. /// /// - [Serializable] public class PipelineClosedException : RuntimeException { #region ctor @@ -430,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 } @@ -448,7 +406,6 @@ 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 @@ -471,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; } @@ -499,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; } @@ -516,47 +467,11 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -624,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 @@ -698,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 /// @@ -712,24 +626,8 @@ public override string Message { get { - return _message ?? (_message = (_wrapperException != null) ? _wrapperException.Message : string.Empty); - } - } - - /// - /// Serializer for - /// - /// Serialization information. - /// Context. - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - if (info == null) - { - throw new PSArgumentNullException(nameof(info)); + return _message ??= (_wrapperException != null) ? _wrapperException.Message : string.Empty; } - - base.GetObjectData(info, context); - info.AddValue("ParentContainsErrorRecordException_Message", this.Message); } #region Private Data @@ -751,7 +649,6 @@ public override void GetObjectData(SerializationInfo info, StreamingContext cont /// /// in the ErrorRecord which contains this exception. /// - [Serializable] public class RedirectedException : RuntimeException { #region constructors @@ -800,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 } @@ -816,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 @@ -868,21 +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. - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override - void GetObjectData(SerializationInfo info, StreamingContext context) { - base.GetObjectData(info, context); + throw new NotSupportedException(); } #endregion Serialization @@ -898,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; } @@ -932,7 +816,6 @@ public int CallDepth /// /// /// - [Serializable] public class PipelineDepthException : SystemException, IContainsErrorRecord { #region ctor @@ -977,21 +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) - { - } - /// - /// Serializer for - /// - /// Serialization information. - /// Context. - [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] - public override - void GetObjectData(SerializationInfo info, StreamingContext context) { - base.GetObjectData(info, context); + throw new NotSupportedException(); } #endregion Serialization @@ -1008,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; } @@ -1049,7 +919,6 @@ 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 @@ -1094,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/ExtensionMethods.cs b/src/System.Management.Automation/utils/ExtensionMethods.cs index 127c9a226cf..f3dc94c047b 100644 --- a/src/System.Management.Automation/utils/ExtensionMethods.cs +++ b/src/System.Management.Automation/utils/ExtensionMethods.cs @@ -12,18 +12,12 @@ internal static class ExtensionMethods { public static void SafeInvoke(this EventHandler eventHandler, object sender, EventArgs eventArgs) { - if (eventHandler != null) - { - eventHandler(sender, eventArgs); - } + eventHandler?.Invoke(sender, eventArgs); } public static void SafeInvoke(this EventHandler eventHandler, object sender, T eventArgs) where T : EventArgs { - if (eventHandler != null) - { - eventHandler(sender, eventArgs); - } + eventHandler?.Invoke(sender, eventArgs); } } @@ -49,10 +43,10 @@ internal static int SequenceGetHashCode(this IEnumerable xs) int hash = 41; // 41 is a random prime number foreach (T x in xs) { - hash = hash * 59; // 59 is a random prime number + hash *= 59; // 59 is a random prime number if (x != null) { - hash = hash + x.GetHashCode(); + hash += x.GetHashCode(); } } diff --git a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs index db68073deb4..87da1d2a1d7 100644 --- a/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs +++ b/src/System.Management.Automation/utils/FormatAndTypeDataHelper.cs @@ -5,9 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; -using System.Management.Automation.Host; using System.Management.Automation.Internal; -using System.Reflection; using System.Text; namespace System.Management.Automation.Runspaces @@ -75,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( @@ -173,7 +171,7 @@ internal static void ThrowExceptionOnError( ConcurrentBag errors, Category category) { - if (errors.Count == 0) + if (errors.IsEmpty) { return; } @@ -210,4 +208,3 @@ internal enum Category } } } - diff --git a/src/System.Management.Automation/utils/FuzzyMatch.cs b/src/System.Management.Automation/utils/FuzzyMatch.cs index 3052cf6e3f2..c4e542a3ca5 100644 --- a/src/System.Management.Automation/utils/FuzzyMatch.cs +++ b/src/System.Management.Automation/utils/FuzzyMatch.cs @@ -1,24 +1,38 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; +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; } /// @@ -28,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); @@ -37,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 7579245980b..ec780222345 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. @@ -55,7 +55,7 @@ private GraphicalHostReflectionWrapper() /// When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly. internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName) { - return GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); + return GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); } /// @@ -73,9 +73,9 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Assembly.Load has been found to throw unadvertised exceptions")] internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName, string featureName) { - GraphicalHostReflectionWrapper returnValue = new GraphicalHostReflectionWrapper(); + GraphicalHostReflectionWrapper returnValue = new(); - if (GraphicalHostReflectionWrapper.IsInputFromRemoting(parentCmdlet)) + if (IsInputFromRemoting(parentCmdlet)) { ErrorRecord error = new ErrorRecord( new NotSupportedException(StringUtil.Format(HelpErrors.RemotingNotSupportedForFeature, featureName)), @@ -87,9 +87,10 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper } // Prepare the full assembly name. - AssemblyName graphicalHostAssemblyName = new AssemblyName(); + AssemblyName smaAssemblyName = typeof(PSObject).Assembly.GetName(); + AssemblyName graphicalHostAssemblyName = new(); graphicalHostAssemblyName.Name = "Microsoft.PowerShell.GraphicalHost"; - graphicalHostAssemblyName.Version = new Version(3, 0, 0, 0); + graphicalHostAssemblyName.Version = smaAssemblyName.Version; graphicalHostAssemblyName.CultureInfo = new CultureInfo(string.Empty); // Neutral culture graphicalHostAssemblyName.SetPublicKeyToken(new byte[] { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }); @@ -124,16 +125,16 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper returnValue._graphicalHostHelperType = returnValue._graphicalHostAssembly.GetType(graphicalHostHelperTypeName); - Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type exists in Microsoft.PowerShell.GraphicalHost"); + Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type should exist in Microsoft.PowerShell.GraphicalHost"); ConstructorInfo constructor = returnValue._graphicalHostHelperType.GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, - new Type[] { }, + Array.Empty(), null); if (constructor != null) { - returnValue._graphicalHostHelperObject = constructor.Invoke(new object[] { }); + returnValue._graphicalHostHelperObject = constructor.Invoke(Array.Empty()); Diagnostics.Assert(returnValue._graphicalHostHelperObject != null, "the constructor does not throw anything"); } @@ -187,7 +188,7 @@ internal object GetPropertyValue(string propertyName) Diagnostics.Assert(_graphicalHostHelperObject != null, "there should be a constructor in order to get an instance property value"); PropertyInfo property = _graphicalHostHelperType.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance); Diagnostics.Assert(property != null, "property " + propertyName + " exists in graphicalHostHelperType is verified by caller"); - return property.GetValue(_graphicalHostHelperObject, new object[] { }); + return property.GetValue(_graphicalHostHelperObject, Array.Empty()); } /// @@ -199,7 +200,7 @@ internal object GetStaticPropertyValue(string propertyName) { PropertyInfo property = _graphicalHostHelperType.GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Static); Diagnostics.Assert(property != null, "property " + propertyName + " exists in graphicalHostHelperType is verified by caller"); - return property.GetValue(null, new object[] { }); + return property.GetValue(null, Array.Empty()); } /// diff --git a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs index c67fcd7e1b7..df36d248d18 100644 --- a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs +++ b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs @@ -10,8 +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 { @@ -19,10 +17,9 @@ class HostException : RuntimeException /// /// Initializes a new instance of the HostException class. /// - public - HostException() : base( - StringUtil.Format(HostInterfaceExceptionsStrings.DefaultCtorMessageTemplate, typeof(HostException).FullName)) + HostException() + : base(StringUtil.Format(HostInterfaceExceptionsStrings.DefaultCtorMessageTemplate, typeof(HostException).FullName)) { SetDefaultErrorRecord(); } @@ -33,9 +30,9 @@ class HostException : RuntimeException /// /// The error message that explains the reason for the exception. /// - public - HostException(string message) : base(message) + HostException(string message) + : base(message) { SetDefaultErrorRecord(); } @@ -52,7 +49,6 @@ class HostException : RuntimeException /// parameter is not a null reference, the current exception is raised in a catch /// block that handles the inner exception. /// - public HostException(string message, Exception innerException) : base(message, innerException) @@ -82,10 +78,13 @@ class HostException : RuntimeException /// /// Intentionally public, third-party hosts can call this /// - public - HostException(string message, Exception innerException, string errorId, ErrorCategory errorCategory) : - base(message, innerException) + HostException( + string message, + Exception innerException, + string errorId, + ErrorCategory errorCategory) + : base(message, innerException) { SetErrorId(errorId); SetErrorCategory(errorCategory); @@ -101,12 +100,13 @@ 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 #region private private void SetDefaultErrorRecord() @@ -121,8 +121,6 @@ private void SetDefaultErrorRecord() /// /// Defines the exception thrown when an error occurs from prompting for a command parameter. /// - - [Serializable] public class PromptingException : HostException { @@ -130,9 +128,9 @@ class PromptingException : HostException /// /// Initializes a new instance of the PromptingException class. /// - public - PromptingException() : base(StringUtil.Format(HostInterfaceExceptionsStrings.DefaultCtorMessageTemplate, typeof(PromptingException).FullName)) + PromptingException() + : base(StringUtil.Format(HostInterfaceExceptionsStrings.DefaultCtorMessageTemplate, typeof(PromptingException).FullName)) { SetDefaultErrorRecord(); } @@ -143,9 +141,9 @@ class PromptingException : HostException /// /// The error message that explains the reason for the exception. /// - public - PromptingException(string message) : base(message) + PromptingException(string message) + : base(message) { SetDefaultErrorRecord(); } @@ -162,7 +160,6 @@ class PromptingException : HostException /// parameter is not a null reference, the current exception is raised in a catch /// block that handles the inner exception. /// - public PromptingException(string message, Exception innerException) : base(message, innerException) @@ -192,10 +189,13 @@ class PromptingException : HostException /// /// Intentionally public, third-party hosts can call this /// - public - PromptingException(string message, Exception innerException, string errorId, ErrorCategory errorCategory) : - base(message, innerException, errorId, errorCategory) + PromptingException( + string message, + Exception innerException, + string errorId, + ErrorCategory errorCategory) + : base(message, innerException, errorId, errorCategory) { } @@ -209,11 +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/IObjectReader.cs b/src/System.Management.Automation/utils/IObjectReader.cs index a4fb4a53f37..c2ef1d6cf32 100644 --- a/src/System.Management.Automation/utils/IObjectReader.cs +++ b/src/System.Management.Automation/utils/IObjectReader.cs @@ -5,8 +5,6 @@ using System.Collections.ObjectModel; using System.Threading; -using Dbg = System.Management.Automation.Diagnostics; - namespace System.Management.Automation.Runspaces { /// diff --git a/src/System.Management.Automation/utils/IObjectWriter.cs b/src/System.Management.Automation/utils/IObjectWriter.cs index 5a6afad9514..49181e34584 100644 --- a/src/System.Management.Automation/utils/IObjectWriter.cs +++ b/src/System.Management.Automation/utils/IObjectWriter.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System; using System.Collections; using System.Threading; @@ -115,7 +114,7 @@ public abstract int MaxCapacity /// /// /// If the enumeration contains elements equal to - /// AutomationNull.Value, they are are ignored. + /// AutomationNull.Value, they are ignored. /// This can cause the return value to be less than the size of /// the collection. /// @@ -124,7 +123,7 @@ public abstract int MaxCapacity internal class DiscardingPipelineWriter : PipelineWriter { - private ManualResetEvent _waitHandle = new ManualResetEvent(true); + private readonly ManualResetEvent _waitHandle = new ManualResetEvent(true); public override WaitHandle WaitHandle { @@ -161,7 +160,7 @@ public override void Flush() public override int Write(object obj) { - int numberOfObjectsWritten = 1; + const int numberOfObjectsWritten = 1; _count += numberOfObjectsWritten; return numberOfObjectsWritten; } @@ -192,4 +191,3 @@ public override int Write(object obj, bool enumerateCollection) } } } - diff --git a/src/System.Management.Automation/utils/MetadataExceptions.cs b/src/System.Management.Automation/utils/MetadataExceptions.cs index 21f9a9b842c..24660d348e7 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,14 +48,20 @@ 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); } - internal MetadataException(string errorId, Exception innerException, string resourceStr, params object[] arguments) : - base(StringUtil.Format(resourceStr, arguments), innerException) + internal MetadataException( + string errorId, + Exception innerException, + string resourceStr, + params object[] arguments) + : base( + StringUtil.Format(resourceStr, arguments), + innerException) { SetErrorCategory(ErrorCategory.MetadataError); SetErrorId(errorId); @@ -65,8 +71,6 @@ internal MetadataException(string errorId, Exception innerException, string reso /// /// Defines the exception thrown for all Validate attributes. /// - [Serializable] - [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class ValidationMetadataException : MetadataException { internal const string ValidateRangeElementType = "ValidateRangeElementType"; @@ -103,7 +107,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. @@ -118,11 +127,15 @@ 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(string errorId, Exception innerException, string resourceStr, params object[] arguments) : - base(errorId, innerException, resourceStr, arguments) + internal ValidationMetadataException( + string errorId, + Exception innerException, + string resourceStr, + params object[] arguments) + : base(errorId, innerException, resourceStr, arguments) { } @@ -151,13 +164,12 @@ internal bool SwallowException get { return _swallowException; } } - private bool _swallowException = false; + private readonly bool _swallowException = false; } /// /// Defines the exception thrown for all ArgumentTransformation attributes. /// - [Serializable] public class ArgumentTransformationMetadataException : MetadataException { internal const string ArgumentTransformationArgumentsShouldBeStrings = "ArgumentTransformationArgumentsShouldBeStrings"; @@ -167,26 +179,40 @@ public class ArgumentTransformationMetadataException : MetadataException /// /// Serialization information. /// Streaming context. - protected ArgumentTransformationMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected ArgumentTransformationMetadataException(SerializationInfo info, StreamingContext context) + { + throw new NotSupportedException(); + } + /// /// Initializes a new instance of ArgumentTransformationMetadataException with the message set /// to typeof(ArgumentTransformationMetadataException).FullName. /// - public ArgumentTransformationMetadataException() : base(typeof(ArgumentTransformationMetadataException).FullName) { } + public ArgumentTransformationMetadataException() + : base(typeof(ArgumentTransformationMetadataException).FullName) { } + /// /// Initializes a new instance of ArgumentTransformationMetadataException setting the message. /// /// The exception's message. - public ArgumentTransformationMetadataException(string message) : base(message) { } + public ArgumentTransformationMetadataException(string message) + : base(message) { } + /// /// Initializes a new instance of ArgumentTransformationMetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public ArgumentTransformationMetadataException(string message, Exception innerException) : base(message, innerException) { } + /// The exception's inner exception. + public ArgumentTransformationMetadataException(string message, Exception innerException) + : base(message, innerException) { } - internal ArgumentTransformationMetadataException(string errorId, Exception innerException, string resourceStr, params object[] arguments) : - base(errorId, innerException, resourceStr, arguments) + internal ArgumentTransformationMetadataException( + string errorId, + Exception innerException, + string resourceStr, + params object[] arguments) + : base(errorId, innerException, resourceStr, arguments) { } } @@ -194,7 +220,6 @@ internal ArgumentTransformationMetadataException(string errorId, Exception inner /// /// Defines the exception thrown for all parameter binding exceptions related to metadata attributes. /// - [Serializable] public class ParsingMetadataException : MetadataException { internal const string ParsingTooManyParameterSets = "ParsingTooManyParameterSets"; @@ -204,28 +229,41 @@ public class ParsingMetadataException : MetadataException /// /// Serialization information. /// Streaming context. - protected ParsingMetadataException(SerializationInfo info, StreamingContext context) : base(info, context) { } + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + protected ParsingMetadataException(SerializationInfo info, StreamingContext context) + { + throw new NotSupportedException(); + } + /// /// Initializes a new instance of ParsingMetadataException with the message set /// to typeof(ParsingMetadataException).FullName. /// - public ParsingMetadataException() : base(typeof(ParsingMetadataException).FullName) { } + public ParsingMetadataException() + : base(typeof(ParsingMetadataException).FullName) { } + /// /// Initializes a new instance of ParsingMetadataException setting the message. /// /// The exception's message. - public ParsingMetadataException(string message) : base(message) { } + public ParsingMetadataException(string message) + : base(message) { } + /// /// Initializes a new instance of ParsingMetadataException setting the message and innerException. /// /// The exception's message. - /// The exceptions's inner exception. - public ParsingMetadataException(string message, Exception innerException) : base(message, innerException) { } + /// The exception's inner exception. + public ParsingMetadataException(string message, Exception innerException) + : base(message, innerException) { } - internal ParsingMetadataException(string errorId, Exception innerException, string resourceStr, params object[] arguments) : - base(errorId, innerException, resourceStr, arguments) + internal ParsingMetadataException( + string errorId, + Exception innerException, + string resourceStr, + params object[] arguments) + : base(errorId, innerException, resourceStr, arguments) { } } } - diff --git a/src/System.Management.Automation/utils/MshArgumentException.cs b/src/System.Management.Automation/utils/MshArgumentException.cs index 26980059c00..452527d1c18 100644 --- a/src/System.Management.Automation/utils/MshArgumentException.cs +++ b/src/System.Management.Automation/utils/MshArgumentException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -70,31 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 /// @@ -123,21 +103,18 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "Argument"; + private readonly string _errorId = "Argument"; /// /// See @@ -152,7 +129,6 @@ public override string Message get { return string.IsNullOrEmpty(_message) ? base.Message : _message; } } - private string _message; + private readonly string _message; } } - diff --git a/src/System.Management.Automation/utils/MshArgumentNullException.cs b/src/System.Management.Automation/utils/MshArgumentNullException.cs index 03ab45654a3..16ef442e5ec 100644 --- a/src/System.Management.Automation/utils/MshArgumentNullException.cs +++ b/src/System.Management.Automation/utils/MshArgumentNullException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -81,30 +79,11 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -121,21 +100,18 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "ArgumentNull"; + private readonly string _errorId = "ArgumentNull"; /// /// See @@ -150,7 +126,6 @@ public override string Message get { return string.IsNullOrEmpty(_message) ? base.Message : _message; } } - private string _message; + private readonly string _message; } } - diff --git a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs index a6bb0dfd3ff..4e65ed0acb1 100644 --- a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs +++ b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -69,29 +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"); + throw new NotSupportedException(); } - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 /// @@ -119,21 +101,17 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "ArgumentOutOfRange"; + private readonly string _errorId = "ArgumentOutOfRange"; } } - diff --git a/src/System.Management.Automation/utils/MshInvalidOperationException.cs b/src/System.Management.Automation/utils/MshInvalidOperationException.cs index 0889fee33df..8400e762360 100644 --- a/src/System.Management.Automation/utils/MshInvalidOperationException.cs +++ b/src/System.Management.Automation/utils/MshInvalidOperationException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -40,28 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 +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; } @@ -138,8 +116,7 @@ internal void SetErrorId(string errorId) _errorId = errorId; } - private ErrorCategory _errorCategory = ErrorCategory.InvalidOperation; - private object _target = null; + private readonly ErrorCategory _errorCategory = ErrorCategory.InvalidOperation; + private readonly object _target = null; } } - diff --git a/src/System.Management.Automation/utils/MshNotImplementedException.cs b/src/System.Management.Automation/utils/MshNotImplementedException.cs index e6a1128a38d..1b925053410 100644 --- a/src/System.Management.Automation/utils/MshNotImplementedException.cs +++ b/src/System.Management.Automation/utils/MshNotImplementedException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -40,28 +38,11 @@ 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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -100,21 +81,17 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "NotImplemented"; + private readonly string _errorId = "NotImplemented"; } } - diff --git a/src/System.Management.Automation/utils/MshNotSupportedException.cs b/src/System.Management.Automation/utils/MshNotSupportedException.cs index e6847ba6d16..a1e519a960e 100644 --- a/src/System.Management.Automation/utils/MshNotSupportedException.cs +++ b/src/System.Management.Automation/utils/MshNotSupportedException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -40,29 +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"); + throw new NotSupportedException(); } - /// - /// Serializer for - /// - /// Serialization information. - /// Streaming context. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 /// @@ -100,21 +82,17 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "NotSupported"; + private readonly string _errorId = "NotSupported"; } } - diff --git a/src/System.Management.Automation/utils/MshObjectDisposedException.cs b/src/System.Management.Automation/utils/MshObjectDisposedException.cs index 93cf76100bb..1bbf1f846d4 100644 --- a/src/System.Management.Automation/utils/MshObjectDisposedException.cs +++ b/src/System.Management.Automation/utils/MshObjectDisposedException.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. using System.Runtime.Serialization; -using System.Security.Permissions; namespace System.Management.Automation { @@ -14,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 { @@ -68,29 +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. - [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)] - 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 @@ -106,21 +87,17 @@ 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; } } private ErrorRecord _errorRecord; - private string _errorId = "ObjectDisposed"; + private readonly string _errorId = "ObjectDisposed"; } } - diff --git a/src/System.Management.Automation/utils/MshTraceSource.cs b/src/System.Management.Automation/utils/MshTraceSource.cs index 9bb0cfbcc3d..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 the 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. /// /// - - Microsoft.NET.Sdk.WindowsDesktop - - - - - - - - - - true - true - + @@ -41,6 +29,10 @@ PreserveNewest PreserveNewest + + PreserveNewest + PreserveNewest + PreserveNewest PreserveNewest @@ -49,6 +41,11 @@ preview\pwsh-preview.cmd PreserveNewest + + en-US\default.help.txt + PreserveNewest + PreserveNewest + @@ -59,8 +56,6 @@ - - - + diff --git a/src/powershell-win-core/runtimeconfig.template.json b/src/powershell-win-core/runtimeconfig.template.json index a3075303ad5..4a5e3e367ec 100644 --- a/src/powershell-win-core/runtimeconfig.template.json +++ b/src/powershell-win-core/runtimeconfig.template.json @@ -1,3 +1,4 @@ +// This is required to roll forward to supported minor.patch versions of the runtime. { - "rollForwardOnNoCandidateFx": 2 + "rollForwardOnNoCandidateFx": 1 } diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index e4d3b51c5d6..1de961674df 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -1,8 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +#nullable enable + using System; using System.IO; +using System.Management.Automation; using System.Reflection; using System.Runtime.InteropServices; @@ -17,7 +20,7 @@ public sealed class ManagedPSEntry /// /// Exception to signify an early startup failure. /// - private class StartupException : Exception + private sealed class StartupException : Exception { /// /// Construct a new startup exception instance. @@ -56,17 +59,17 @@ public StartupException(string callName, int exitCode) #endif /// - /// Starts the managed MSH. + /// Starts PowerShell. /// /// - /// Command line arguments to the managed MSH + /// Command line arguments to PowerShell /// public static int Main(string[] args) { #if UNIX AttemptExecPwshLogin(args); #endif - return UnmanagedPSEntry.Start(string.Empty, args, args.Length); + return UnmanagedPSEntry.Start(args, args.Length); } #if UNIX @@ -87,13 +90,13 @@ private static void AttemptExecPwshLogin(string[] args) return; } - bool isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + bool isLinux = Platform.IsLinux; // The first byte (ASCII char) of the name of this process, used to detect '-' for login byte procNameFirstByte; // The path to the executable this process was started from - string pwshPath; + string? pwshPath; // On Linux, we can simply use the /proc filesystem if (isLinux) @@ -116,6 +119,8 @@ private static void AttemptExecPwshLogin(string[] args) pwshPath = Marshal.PtrToStringAnsi(linkPathPtr, (int)bufSize); Marshal.FreeHGlobal(linkPathPtr); + ArgumentNullException.ThrowIfNull(pwshPath); + // exec pwsh ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: false)); return; @@ -124,10 +129,7 @@ private static void AttemptExecPwshLogin(string[] args) // At this point, we are on macOS // Set up the mib array and the query for process maximum args size - Span mib = stackalloc int[3]; - int mibLength = 2; - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_ARGMAX; + Span mib = [MACOS_CTL_KERN, MACOS_KERN_ARGMAX]; int size = IntPtr.Size / 2; int argmax = 0; @@ -136,7 +138,7 @@ private static void AttemptExecPwshLogin(string[] args) { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mibLength, &argmax, &size, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mib.Length, &argmax, &size, IntPtr.Zero, 0)); } } @@ -150,16 +152,13 @@ private static void AttemptExecPwshLogin(string[] args) IntPtr executablePathPtr = IntPtr.Zero; try { - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_PROCARGS2; - mib[2] = pid; - mibLength = 3; + mib = new int[] { MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid }; unsafe { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mibLength, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mib.Length, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); } // The memory block we're reading is a series of null-terminated strings @@ -178,7 +177,6 @@ private static void AttemptExecPwshLogin(string[] args) // We can reuse this value later to prevent needing to call a .NET API // to generate our exec invocation. - // We don't care about argc's value, since argv[0] must always exist. // Skip over argc, but remember where exec_path is for later executablePathPtr = IntPtr.Add(procargs, sizeof(int)); @@ -200,6 +198,8 @@ private static void AttemptExecPwshLogin(string[] args) // Get the pwshPath from exec_path pwshPath = Marshal.PtrToStringAnsi(executablePathPtr); + ArgumentNullException.ThrowIfNull(pwshPath); + // exec pwsh ThrowOnFailure("exec", ExecPwshLogin(args, pwshPath, isMacOS: true)); } @@ -255,8 +255,8 @@ private static bool IsParam( // Check arg chars in order and allow prefixes for (int i = 1; i < arg.Length; i++) { - if (arg[i] != paramToCheck[i-1] - && arg[i] != paramToCheckUpper[i-1]) + if (arg[i] != paramToCheck[i - 1] + && arg[i] != paramToCheckUpper[i - 1]) { return false; } @@ -281,20 +281,20 @@ private static int ExecPwshLogin(string[] args, string pwshPath, bool isMacOS) int quotedPwshPathLength = GetQuotedPathLength(pwshPath); string pwshInvocation = string.Create( - quotedPwshPathLength + 10, // exec '{pwshPath}' "$@" + quotedPwshPathLength + 10, // exec '{pwshPath}' "$@" (pwshPath, quotedPwshPathLength), CreatePwshInvocation); // Set up the arguments for '/bin/sh'. // We need to add 5 slots for the '/bin/sh' invocation parts, plus 1 slot for the null terminator at the end - var execArgs = new string[args.Length + 6]; + var execArgs = new string?[args.Length + 6]; // The command arguments // First argument is the command name. // Even when executing 'zsh', we want to set this to '/bin/sh' // because this tells 'zsh' to run in sh emulation mode (it examines $0) - execArgs[0] = "/bin/sh"; + execArgs[0] = "/bin/sh"; execArgs[1] = "-l"; // Login flag execArgs[2] = "-c"; // Command parameter @@ -309,7 +309,7 @@ private static int ExecPwshLogin(string[] args, string pwshPath, bool isMacOS) // // Since command_name is ignored and we can't use null (it's the terminator) // we use empty string - execArgs[4] = ""; + execArgs[4] = string.Empty; // Add the arguments passed to pwsh on the end. args.CopyTo(execArgs, 5); @@ -342,7 +342,11 @@ private static int GetQuotedPathLength(string str) foreach (char c in str) { length++; - if (c == '\'') { length++; } + + if (c == '\'') + { + length++; + } } return length; @@ -359,7 +363,7 @@ private static void CreatePwshInvocation( (string path, int quotedLength) invocationInfo) { // "exec " - string prefix = "exec "; + const string prefix = "exec "; prefix.AsSpan().CopyTo(strBuf); // The quoted path to pwsh, like "'/opt/microsoft/powershell/7/pwsh'" @@ -369,7 +373,7 @@ private static void CreatePwshInvocation( i += invocationInfo.quotedLength; // ' "$@"' the argument vector splat to pass pwsh arguments through - string suffix = " \"$@\""; + const string suffix = " \"$@\""; Span bufSuffix = strBuf.Slice(i); suffix.AsSpan().CopyTo(bufSuffix); } @@ -433,7 +437,7 @@ private static void ThrowOnFailure(string call, int code) CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, SetLastError = true)] - private static extern int Exec(string path, string[] args); + private static extern int Exec(string path, string?[] args); /// /// The `readlink` POSIX syscall we use to read the symlink from /proc/self/exe @@ -490,7 +494,7 @@ private static void ThrowOnFailure(string call, int code) CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, SetLastError = true)] - private static unsafe extern int SysCtl(int *mib, int mibLength, void *oldp, int *oldlenp, IntPtr newp, int newlenp); + private static extern unsafe int SysCtl(int *mib, int mibLength, void *oldp, int *oldlenp, IntPtr newp, int newlenp); #endif } } diff --git a/stylecop.json b/stylecop.json index 77517f2095b..9653436690c 100644 --- a/stylecop.json +++ b/stylecop.json @@ -25,7 +25,7 @@ }, "namingRules" : { "allowCommonHungarianPrefixes" : true, - "allowedHungarianPrefixes" : [ "n", "r", "l", "i", "io", "is", "fs", "lp", "dw", "h", "rs", "ps", "op", "sb", "my" ] + "allowedHungarianPrefixes" : [ "n", "r", "l", "i", "io", "is", "fs", "lp", "dw", "h", "rs", "ps", "op", "sb", "my", "vt" ] }, "maintainabilityRules" : { "topLevelTypes" : [ diff --git a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 index 40fa0ec1027..1aa87ba3d69 100644 --- a/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 +++ b/test/SSHRemoting/SSHRemoting.Basic.Tests.ps1 @@ -6,72 +6,209 @@ Describe "SSHRemoting Basic Tests" -tags CI { # SSH remoting is set up to automatically authenticate current user via SSH keys # All tests connect back to localhost machine + $script:TestConnectingTimeout = 5000 # Milliseconds + + function RestartSSHDService + { + if ($IsWindows) + { + Write-Verbose -Verbose "Restarting Windows SSHD service..." + Restart-Service sshd + Write-Verbose -Verbose "SSHD service status: $(Get-Service sshd | Out-String)" + } + else + { + Write-Verbose -Verbose "Restarting Unix SSHD service..." + sudo service ssh restart + $status = sudo service ssh status + Write-Verbose -Verbose "SSHD service status: $status" + } + } + + function TryNewPSSession + { + param( + [string[]] $HostName, + [string[]] $Name, + [int] $Port, + [string] $UserName, + [string] $KeyFilePath, + [string] $Subsystem + ) + + Write-Verbose -Verbose "Starting TryNewPSSession ..." + + # Try creating a new SSH connection + $timeout = $script:TestConnectingTimeout + $connectionError = $null + $session = $null + $count = 0 + while (($null -eq $session) -and ($count++ -lt 2)) + { + $session = New-PSSession @PSBoundParameters -ConnectingTimeout $timeout -ErrorVariable connectionError -ErrorAction SilentlyContinue + if ($null -eq $session) + { + Write-Verbose -Verbose "SSH New-PSSession remoting connect failed." + + if ($count -eq 1) + { + # Try restarting sshd service + RestartSSHDService + } + } + } + + if ($null -eq $session) + { + $message = "New-PSSession unable to connect to SSH remoting endpoint after two attempts. Error: $($connectionError.Exception.Message)" + throw [System.Management.Automation.PSInvalidOperationException]::new($message) + } + + Write-Verbose -Verbose "SSH New-PSSession remoting connect succeeded." + Write-Output $session + } + + function TryNewPSSessionHash + { + param ( + [hashtable[]] $SSHConnection, + [string[]] $Name + ) + + Write-Verbose -Verbose "Starting TryNewPSSessionHash ..." + + foreach ($connect in $SSHConnection) + { + $connect.Add('ConnectingTimeout', $script:TestConnectingTimeout) + } + + # Try creating a new SSH connection + $connectionError = $null + $session = $null + $count = 0 + while (($null -eq $session) -and ($count++ -lt 2)) + { + $session = New-PSSession @PSBoundParameters -ErrorVariable connectionError -ErrorAction SilentlyContinue + if ($null -eq $session) + { + Write-Verbose -Verbose "SSH New-PSSession remoting connect failed." + + if ($count -eq 1) + { + # Try restarting sshd service + RestartSSHDService + } + } + } + + if ($null -eq $session) + { + $message = "New-PSSession unable to connect to SSH remoting endpoint after two attempts. Error: $($connectionError.Exception.Message)" + throw [System.Management.Automation.PSInvalidOperationException]::new($message) + } + + Write-Verbose -Verbose "SSH New-PSSession remoting connect succeeded." + Write-Output $session + } + function VerifySession { param ( [System.Management.Automation.Runspaces.PSSession] $session ) + if ($null -eq $session) + { + return + } + + Write-Verbose -Verbose "VerifySession called for session: $($session.Id)" + $session.State | Should -BeExactly 'Opened' $session.ComputerName | Should -BeExactly 'localhost' $session.Transport | Should -BeExactly 'SSH' + Write-Verbose -Verbose "Invoking whoami" Invoke-Command -Session $session -ScriptBlock { whoami } | Should -BeExactly $(whoami) + Write-Verbose -Verbose "Invoking PSSenderInfo" $psRemoteVersion = Invoke-Command -Session $session -ScriptBlock { $PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion } $psRemoteVersion.Major | Should -BeExactly $PSVersionTable.PSVersion.Major $psRemoteVersion.Minor | Should -BeExactly $PSVersionTable.PSVersion.Minor + Write-Verbose -Verbose "VerifySession complete" } Context "New-PSSession Tests" { AfterEach { + Write-Verbose -Verbose "Starting New-PSSession AfterEach" if ($script:session -ne $null) { Remove-PSSession -Session $script:session } if ($script:sessions -ne $null) { Remove-PSSession -Session $script:sessions } + Write-Verbose -Verbose "AfterEach complete" } It "Verifies new connection with implicit current User" { - $script:session = New-PSSession -HostName localhost -ErrorVariable err - $err | Should -HaveCount 0 + Write-Verbose -Verbose "It Starting: Verifies new connection with implicit current User" + $script:session = TryNewPSSession -HostName localhost + $script:session | Should -Not -BeNullOrEmpty VerifySession $script:session + Write-Verbose -Verbose "It Complete" } It "Verifies new connection with explicit User parameter" { - $script:session = New-PSSession -HostName localhost -UserName (whoami) -ErrorVariable err - $err | Should -HaveCount 0 + Write-Verbose -Verbose "It Starting: Verifies new connection with explicit User parameter" + $script:session = TryNewPSSession -HostName localhost -UserName (whoami) + $script:session | Should -Not -BeNullOrEmpty VerifySession $script:session + Write-Verbose -Verbose "It Complete" } It "Verifies explicit Name parameter" { + Write-Verbose -Verbose "It Starting: Verifies explicit Name parameter" $sessionName = 'TestSessionNameA' - $script:session = New-PSSession -HostName localhost -Name $sessionName -ErrorVariable err - $err | Should -HaveCount 0 + $script:session = TryNewPSSession -HostName localhost -Name $sessionName + $script:session | Should -Not -BeNullOrEmpty VerifySession $script:session $script:session.Name | Should -BeExactly $sessionName + Write-Verbose -Verbose "It Complete" } It "Verifies explicit Port parameter" { + Write-Verbose -Verbose "It Starting: Verifies explicit Port parameter" $portNum = 22 - $script:session = New-PSSession -HostName localhost -Port $portNum -ErrorVariable err + $script:session = TryNewPSSession -HostName localhost -Port $portNum + $script:session | Should -Not -BeNullOrEmpty + VerifySession $script:session + Write-Verbose -Verbose "It Complete" + } + + It "Verifies explicit Options parameter" { + $options = @{"Port"="22"} + $script:session = New-PSSession -HostName localhost -Options $options -ErrorVariable err $err | Should -HaveCount 0 VerifySession $script:session } It "Verifies explicit Subsystem parameter" { + Write-Verbose -Verbose "It Starting: Verifies explicit Subsystem parameter" $portNum = 22 $subSystem = 'powershell' - $script:session = New-PSSession -HostName localhost -Port $portNum -SubSystem $subSystem -ErrorVariable err - $err | Should -HaveCount 0 + $script:session = TryNewPSSession -HostName localhost -Port $portNum -SubSystem $subSystem + $script:session | Should -Not -BeNullOrEmpty VerifySession $script:session + Write-Verbose -Verbose "It Complete" } It "Verifies explicit KeyFilePath parameter" { + Write-Verbose -Verbose "It Starting: Verifies explicit KeyFilePath parameter" $keyFilePath = "$HOME/.ssh/id_rsa" $portNum = 22 $subSystem = 'powershell' - $script:session = New-PSSession -HostName localhost -Port $portNum -SubSystem $subSystem -KeyFilePath $keyFilePath -ErrorVariable err - $err | Should -HaveCount 0 + $script:session = TryNewPSSession -HostName localhost -Port $portNum -SubSystem $subSystem -KeyFilePath $keyFilePath + $script:session | Should -Not -BeNullOrEmpty VerifySession $script:session + Write-Verbose -Verbose "It Complete" } It "Verifies SSHConnection hash table parameters" { + Write-Verbose -Verbose "It Starting: Verifies SSHConnection hash table parameters" $sshConnection = @( @{ HostName = 'localhost' @@ -85,14 +222,81 @@ Describe "SSHRemoting Basic Tests" -tags CI { KeyFilePath = "$HOME/.ssh/id_rsa" Subsystem = 'powershell' }) - $script:sessions = New-PSSession -SSHConnection $sshConnection -Name 'Connection1','Connection2' -ErrorVariable err - $err | Should -HaveCount 0 + $script:sessions = TryNewPSSessionHash -SSHConnection $sshConnection -Name 'Connection1','Connection2' $script:sessions | Should -HaveCount 2 $script:sessions[0].Name | Should -BeLike 'Connection*' $script:sessions[1].Name | Should -BeLike 'Connection*' VerifySession $script:sessions[0] VerifySession $script:sessions[1] + Write-Verbose -Verbose "It Complete" + } + + It "Verifies the 'pwshconfig' configured endpoint." { + Write-Verbose -Verbose "It Starting: Verifies the 'pwshconfig' configured endpoint." + $script:session = TryNewPSSession -HostName localhost -Subsystem 'pwshconfig' + $script:session | Should -Not -BeNullOrEmpty + # Configured session should be in ConstrainedLanguage mode. + $sessionLangMode = Invoke-Command -Session $script:session -ScriptBlock { "$($ExecutionContext.SessionState.LanguageMode)" } + $sessionLangMode | Should -BeExactly "ConstrainedLanguage" + Write-Verbose -Verbose "It Complete" + } + + <# + It "Verifes that 'pwshbroken' throws expected error for missing config file." { + Write-Verbose -Verbose "It Starting: Verifes that 'pwshbroken' throws expected error for missing config file." + { $script:session = TryNewPSSession -HostName localhost -Subsystem 'pwshbroken' } | Should -Throw + $script:session = $null + Write-Verbose -Verbose "It Complete" + } + #> + } + + function TryCreateRunspace + { + param ( + [string] $UserName, + [string] $ComputerName, + [string] $KeyFilePath, + [int] $Port, + [string] $Subsystem + ) + + Write-Verbose -Verbose "Starting TryCreateRunspace ..." + + $timeout = $script:TestConnectingTimeout + $connectionError = $null + $count = 0 + $rs = $null + $ci = [System.Management.Automation.Runspaces.SSHConnectionInfo]::new($UserName, $ComputerName, $KeyFilePath, $Port, $Subsystem, $timeout) + while (($null -eq $rs) -and ($count++ -lt 2)) + { + try + { + $rs = [runspacefactory]::CreateRunspace($host, $ci) + $null = $rs.Open() + } + catch + { + $connectionError = $_ + $rs = $null + Write-Verbose -Verbose "SSH Runspace Open remoting connect failed." + + if ($count -eq 1) + { + # Try restarting sshd service + RestartSSHDService + } + } } + + if (($null -eq $rs) -or !($rs -is [runspace])) + { + $message = "Runspace open unable to connect to SSH remoting endpoint after two attempts. Error: $($connectionError.Message)" + throw [System.Management.Automation.PSInvalidOperationException]::new($message) + } + + Write-Verbose -Verbose "SSH Runspace Open remoting connect succeeded." + Write-Output $rs } function VerifyRunspace { @@ -100,19 +304,29 @@ Describe "SSHRemoting Basic Tests" -tags CI { [runspace] $rs ) + if ($null -eq $rs) + { + return + } + + Write-Verbose -Verbose "VerifyRunspace called for runspace: $($rs.Id)" + $rs.RunspaceStateInfo.State | Should -BeExactly 'Opened' $rs.RunspaceAvailability | Should -BeExactly 'Available' $rs.RunspaceIsRemote | Should -BeTrue $ps = [powershell]::Create() try { + Write-Verbose -Verbose "VerifyRunspace: Invoking PSSenderInfo" $ps.Runspace = $rs $psRemoteVersion = $ps.AddScript('$PSSenderInfo.ApplicationArguments.PSVersionTable.PSVersion').Invoke() $psRemoteVersion.Major | Should -BeExactly $PSVersionTable.PSVersion.Major $psRemoteVersion.Minor | Should -BeExactly $PSVersionTable.PSVersion.Minor $ps.Commands.Clear() + Write-Verbose -Verbose "VerifyRunspace: Invoking whoami" $ps.AddScript('whoami').Invoke() | Should -BeExactly $(whoami) + Write-Verbose -Verbose "VerifyRunspace complete" } finally { @@ -123,7 +337,9 @@ Describe "SSHRemoting Basic Tests" -tags CI { Context "SSH Remoting API Tests" { AfterEach { - if ($script:rs -ne $null) { $script:rs.Dispose() } + Write-Verbose -Verbose "Starting Runspace close AfterEach" + if (($script:rs -ne $null) -and ($script:rs -is [runspace])) { $script:rs.Dispose() } + Write-Verbose -Verbose "AfterEach complete" } $testCases = @( @@ -175,13 +391,15 @@ Describe "SSHRemoting Basic Tests" -tags CI { $ComputerName, $KeyFilePath, $Port, - $SubSystem + $SubSystem, + $TestName ) - $ci = [System.Management.Automation.Runspaces.SSHConnectionInfo]::new($UserName, $ComputerName, $KeyFilePath, $Port, $Subsystem) - $script:rs = [runspacefactory]::CreateRunspace($host, $ci) - $script:rs.Open() + Write-Verbose -Verbose "It Starting: $TestName" + $script:rs = TryCreateRunspace -UserName $UserName -ComputerName $ComputerName -KeyFilePath $KeyFilePath -Port $Port -Subsystem $Subsystem + $script:rs | Should -Not -BeNullOrEmpty VerifyRunspace $script:rs + Write-Verbose -Verbose "It Complete" } } } diff --git a/test/Test.Common.props b/test/Test.Common.props index 777680552a4..8a5522e9eaa 100644 --- a/test/Test.Common.props +++ b/test/Test.Common.props @@ -1,15 +1,27 @@ + + PowerShell Test Microsoft Corporation (c) Microsoft Corporation. - netcoreapp5.0 - 8.0 + net11.0 + preview true true true + true + true + + + + strict + + + + true diff --git a/test/common/markdown/.gitignore b/test/common/markdown/.gitignore deleted file mode 100644 index c2658d7d1b3..00000000000 --- a/test/common/markdown/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/test/common/markdown/gulpfile.js b/test/common/markdown/gulpfile.js deleted file mode 100644 index 90014969b7d..00000000000 --- a/test/common/markdown/gulpfile.js +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -function runTest() { - "use strict"; - var gulp = require("gulp"); - var concat = require("gulp-concat"); - var through2 = require("through2"); - var markdownlint = require("markdownlint"); - - gulp.task("test-mdsyntax", function task() { - var paths = []; - var rootpath; - - // assign --repoRoot into rootpath - var j = process.argv.indexOf("--rootpath"); - if (j > -1) { - rootpath = process.argv[j + 1]; - } - - if (rootpath === null) { - throw "--rootpath must be specified before all other parameters"; - } - - // parse --filter into paths. --rootpath must be specified first. - j = process.argv.indexOf("--filter"); - if (j > -1) { - var filters = process.argv[j + 1].split(","); - filters.forEach(function(filter) { - paths.push(rootpath + "/" + filter); - }, this); - } - - if (paths.length === 0) { - throw "--filter must be specified"; - } - - var rootJsonFile = rootpath + "/.markdownlint.json"; - var fs = require("fs"); - fs.appendFileSync("markdownissues.txt", "--EMPTY--\r\n"); - return gulp.src(paths, { "read": false }) - .pipe(through2.obj(function obj(file, enc, next) { - markdownlint({ - "files": [file.path], - "config": require(rootJsonFile) - }, - function callback(err, result) { - var resultString = (result || "").toString(); - if (resultString) { - file.contents = Buffer.from(resultString); - } - next(err, file); - }); - })) - .pipe(concat("markdownissues.txt", { newLine: "\r\n" })) - .pipe(gulp.dest(".")); - }); -} - -runTest(); diff --git a/test/common/markdown/markdown-link.tests.ps1 b/test/common/markdown/markdown-link.tests.ps1 deleted file mode 100644 index 21c3632b530..00000000000 --- a/test/common/markdown/markdown-link.tests.ps1 +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Describe "Verify Markdown Links" { - BeforeAll { - if(!(Get-Command -Name 'markdown-link-check' -ErrorAction SilentlyContinue)) - { - Write-Verbose "installing markdown-link-check ..." -Verbose - start-nativeExecution { - sudo yarn global add markdown-link-check@3.7.2 - } - } - - if(!(Get-Module -Name 'ThreadJob' -ListAvailable -ErrorAction SilentlyContinue)) - { - Install-Module -Name ThreadJob -Scope CurrentUser - } - - # Cleanup jobs for reliability - Get-Job | Remove-Job -Force - } - - AfterAll { - # Cleanup jobs to leave the process the same - Get-Job | Remove-Job -Force - } - - $groups = Get-ChildItem -Path "$PSScriptRoot\..\..\..\*.md" -Recurse | Where-Object {$_.DirectoryName -notlike '*node_modules*'} | Group-Object -Property directory - - $jobs = @{} - # start all link verification in parallel - Foreach($group in $groups) - { - Write-Verbose -Verbose "starting jobs for $($group.Name) ..." - $job = Start-ThreadJob { - param([object] $group) - foreach($file in $group.Group) - { - $results = markdown-link-check $file 2>&1 - Write-Output ([PSCustomObject]@{ - file = $file - results = $results - }) - } - } -ArgumentList @($group) - $jobs.add($group.name,$job) - } - - Write-Verbose -Verbose "Getting results ..." - # Get the results and verify - foreach($key in $jobs.keys) - { - $job = $jobs.$key - $results = Receive-Job -Job $job -Wait - Remove-Job -Job $Job - foreach($jobResult in $results) - { - $file = $jobResult.file - $result = $jobResult.results - Context "Verify links in $file" { - # failures look like `[✖] https://someurl` (perhaps without the https://) - # passes look like `[✓] https://someurl` (perhaps without the https://) - $failures = $result -like '*[✖]*' | ForEach-Object { $_.Substring(4).Trim() } - $passes = $result -like '*[✓]*' | ForEach-Object { - @{url=$_.Substring(4).Trim() } - } - $trueFailures = @() - $verifyFailures = @() - foreach ($failure in $failures) { - if($failure -like 'https://www.amazon.com*') - { - # In testing amazon links often failed when they are valid - # Verify manually - $verifyFailures += @{url = $failure} - } - else - { - $trueFailures += @{url = $failure} - } - } - - # must have some code in the test for it to pass - function noop { - } - - if($passes) - { - It " should work" -TestCases $passes { - noop - } - } - - if($trueFailures) - { - It " should work" -TestCases $trueFailures { - param($url) - - # there could be multiple reasons why a failure is ok - # check against the allowed failures - $allowedFailures = [System.Net.HttpStatusCode[]]( - 503, # Service Unavailable - 504 # Gateway Timeout - ) - - $prefix = $url.Substring(0,7) - - # Logging for diagnosability. Azure DevOps sometimes redacts the full url. - Write-Verbose "prefix: '$prefix'" - if($url -match '^http(s)?:') - { - # If invoke-WebRequest can handle the URL, re-verify, with 6 retries - try - { - $null = Invoke-WebRequest -Uri $url -RetryIntervalSec 10 -MaximumRetryCount 6 - } - catch [Microsoft.PowerShell.Commands.HttpResponseException] - { - if ( $allowedFailures -notcontains $_.Exception.Response.StatusCode ) { - throw "Failed to complete request to `"$url`". $($_.Exception.Message)" - } - } - } - else { - throw "Tool reported URL as unreachable." - } - } - } - - if($verifyFailures) - { - It " should work" -TestCases $verifyFailures -Pending { - } - } - - if(!$passes -and !$trueFailures -and !$verifyFailures) - { - It "has no links" { - noop - } - } - } - } - } -} diff --git a/test/common/markdown/markdown.tests.ps1 b/test/common/markdown/markdown.tests.ps1 deleted file mode 100644 index ee152b5c703..00000000000 --- a/test/common/markdown/markdown.tests.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Import-Module HelpersCommon -$moduleRootFilePath = Split-Path -Path $PSScriptRoot -Parent - -# Identify the repository root path of the resource module -$repoRootPath = (Resolve-Path -LiteralPath (Join-Path $moduleRootFilePath "../..")).ProviderPath -$repoRootPathFound = $false - -Describe 'Common Tests - Validate Markdown Files' -Tag 'CI' { - BeforeAll { - Push-Location $psscriptroot - $skip = $false - $NpmInstalled = "not installed" - if (Get-Command -Name 'yarn' -ErrorAction SilentlyContinue) - { - $NpmInstalled = "Installed" - Write-Verbose -Message "Checking if Gulp is installed. This may take a few moments." -Verbose - start-nativeExecution { yarn } - if(!(Get-Command -Name 'gulp' -ErrorAction SilentlyContinue)) - { - start-nativeExecution { - sudo yarn global add 'gulp@4.0.2' - } - } - if(!(Get-Command -Name 'node' -ErrorAction SilentlyContinue)) - { - throw "node not found" - } - } - if(!(Get-Command -Name 'node' -ErrorAction SilentlyContinue)) - { - <# - On Windows, pre-requisites are missing - For now we will skip, and write a warning. Work to resolve this is tracked in: - https://github.com/PowerShell/PowerShell/issues/3429 - #> - Write-Warning "Node and yarn are required to run this test" - $skip = $true - } - - $mdIssuesPath = Join-Path -Path $PSScriptRoot -ChildPath "markdownissues.txt" - Remove-Item -Path $mdIssuesPath -Force -ErrorAction SilentlyContinue - } - - AfterAll { - Pop-Location - } - - It "Should not have errors in any markdown files" -Skip:$skip { - $NpmInstalled | Should -BeExactly "Installed" - $mdErrors = 0 - Push-Location -Path $PSScriptRoot - try - { - $docsToTest = @( - './.github/*.md' - './README.md' - './demos/python/*.md' - './docker/*.md' - './docs/building/*.md' - './docs/community/*.md' - './docs/host-powershell/*.md' - './docs/cmdlet-example/*.md' - './docs/maintainers/*.md' - './test/powershell/README.md' - './tools/*.md' - './.github/ISSUE_TEMPLATE/*.md' - ) - $filter = ($docsToTest -join ',') - - # Gulp 4 beta is returning non-zero exit code even when there is not an error - Start-NativeExecution { - &"gulp" test-mdsyntax --silent ` - --rootpath $repoRootPath ` - --filter $filter - } -VerboseOutputOnError -IgnoreExitcode - - } - finally - { - Pop-Location - } - - $mdIssuesPath | Should -Exist - - [string[]] $markdownErrors = Get-Content -Path $mdIssuesPath - Remove-Item -Path $mdIssuesPath -Force -ErrorAction SilentlyContinue - - if ($markdownErrors -ne "--EMPTY--") - { - $markdownErrors += ' (See https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md for an explanation of the error codes)' - } - - $markdownErrors | Write-Host - - $markdownErrors -join "`n" | Should -BeExactly "--EMPTY--" - } -} diff --git a/test/common/markdown/package.json b/test/common/markdown/package.json deleted file mode 100644 index f5d23fa8a59..00000000000 --- a/test/common/markdown/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "powershell.common.markdown.tests", - "private": true, - "version": "1.0.0", - "description": "The PowerShell Common Markdown Tests.", - "main": "gulpfile.js", - "dependencies": { - "gulp": "^4.0.2", - "markdownlint": "^0.20.2", - "through2": "^3.0.1" - }, - "devDependencies": { - "gulp-concat": "^2.6.1", - "gulp-debug": "^4.0.0" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/PowerShell/PowerShell.git" - }, - "author": "Microsoft Corporation", - "license": "MIT", - "bugs": { - "url": "https://github.com/PowerShell/PowerShell/issues" - }, - "homepage": "https://github.com/PowerShell/PowerShell#readme" -} diff --git a/test/common/markdown/yarn.lock b/test/common/markdown/yarn.lock deleted file mode 100644 index 7b1055e2836..00000000000 --- a/test/common/markdown/yarn.lock +++ /dev/null @@ -1,2337 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -ansi-colors@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" - integrity sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA== - dependencies: - ansi-wrap "^0.1.0" - -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE= - dependencies: - ansi-wrap "0.1.0" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-wrap@0.1.0, ansi-wrap@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -append-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" - integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE= - dependencies: - buffer-equal "^1.0.0" - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-filter@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" - integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= - dependencies: - make-iterator "^1.0.0" - -arr-flatten@^1.0.1, arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-map@^2.0.0, arr-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" - integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= - dependencies: - make-iterator "^1.0.0" - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-each@^1.0.0, array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= - -array-initial@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" - integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= - dependencies: - array-slice "^1.0.0" - is-number "^4.0.0" - -array-last@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" - integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== - dependencies: - is-number "^4.0.0" - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== - -array-sort@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" - integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== - dependencies: - default-compare "^1.0.0" - get-value "^2.0.6" - kind-of "^5.0.2" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -async-done@^1.2.0, async-done@^1.2.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.2.tgz#5e15aa729962a4b07414f528a88cdf18e0b290a2" - integrity sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.2" - process-nextick-args "^2.0.0" - stream-exhaust "^1.0.1" - -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - -async-settle@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" - integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= - dependencies: - async-done "^1.2.2" - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -bach@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" - integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= - dependencies: - arr-filter "^1.1.1" - arr-flatten "^1.0.1" - arr-map "^2.0.0" - array-each "^1.0.0" - array-initial "^1.0.0" - array-last "^1.1.1" - async-done "^1.2.2" - async-settle "^1.0.0" - now-and-later "^2.0.0" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -buffer-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" - integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74= - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - -chalk@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chokidar@^2.0.0: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= - -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= - -clone@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= - -cloneable-readable@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec" - integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ== - dependencies: - inherits "^2.0.1" - process-nextick-args "^2.0.0" - readable-stream "^2.3.5" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -collection-map@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" - integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= - dependencies: - arr-map "^2.0.2" - for-own "^1.0.0" - make-iterator "^1.0.0" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -concat-with-sourcemaps@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" - integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== - dependencies: - source-map "^0.6.1" - -convert-source-map@^1.5.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" - integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== - dependencies: - safe-buffer "~5.1.1" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -copy-props@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe" - integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A== - dependencies: - each-props "^1.3.0" - is-plain-object "^2.0.1" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -d@1, d@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" - integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== - dependencies: - es5-ext "^0.10.50" - type "^1.0.1" - -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -decamelize@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -default-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" - integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== - dependencies: - kind-of "^5.0.2" - -default-resolution@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" - integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= - -define-properties@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" - integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== - dependencies: - object-keys "^1.0.12" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - -duplexify@^3.6.0: - version "3.7.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" - integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -each-props@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333" - integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA== - dependencies: - is-plain-object "^2.0.1" - object.defaults "^1.1.0" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -entities@~2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.2.tgz#ac74db0bba8d33808bbf36809c3a5c3683531436" - integrity sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw== - -error-ex@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50: - version "0.10.53" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" - integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.3" - next-tick "~1.0.0" - -es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" - integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== - dependencies: - d "^1.0.1" - ext "^1.1.2" - -es6-weak-map@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" - integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== - dependencies: - d "1" - es5-ext "^0.10.46" - es6-iterator "^2.0.3" - es6-symbol "^3.1.1" - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - -ext@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" - integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== - dependencies: - type "^2.0.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fancy-log@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" - integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== - dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - parse-node-version "^1.0.0" - time-stamp "^1.0.0" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - integrity sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -findup-sync@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" - integrity sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw= - dependencies: - detect-file "^1.0.0" - is-glob "^3.1.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -fined@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" - integrity sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng== - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -flagged-respawn@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.1.tgz#e7de6f1279ddd9ca9aac8a5971d618606b3aab41" - integrity sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q== - -flush-write-stream@^1.0.2: - version "1.1.1" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" - integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== - dependencies: - inherits "^2.0.3" - readable-stream "^2.3.6" - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fs-mkdirp-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" - integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes= - dependencies: - graceful-fs "^4.1.11" - through2 "^2.0.3" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - -get-own-enumerable-property-symbols@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" - integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-stream@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" - integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ= - dependencies: - extend "^3.0.0" - glob "^7.1.1" - glob-parent "^3.1.0" - is-negated-glob "^1.0.0" - ordered-read-streams "^1.0.0" - pumpify "^1.3.5" - readable-stream "^2.1.5" - remove-trailing-separator "^1.0.1" - to-absolute-glob "^2.0.0" - unique-stream "^2.0.2" - -glob-watcher@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" - integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== - dependencies: - anymatch "^2.0.0" - async-done "^1.2.0" - chokidar "^2.0.0" - is-negated-glob "^1.0.0" - just-debounce "^1.0.0" - object.defaults "^1.1.0" - -glob@^7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -glogg@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" - integrity sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== - dependencies: - sparkles "^1.0.0" - -graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.2.4" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" - integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== - -gulp-cli@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.1.tgz#376e427661b7996430a89d71c15df75defa3360a" - integrity sha512-yEMxrXqY8mJFlaauFQxNrCpzWJThu0sH1sqlToaTOT063Hub9s/Nt2C+GSLe6feQ/IMWrHvGOOsyES7CQc9O+A== - dependencies: - ansi-colors "^1.0.1" - archy "^1.0.0" - array-sort "^1.0.0" - color-support "^1.1.3" - concat-stream "^1.6.0" - copy-props "^2.0.1" - fancy-log "^1.3.2" - gulplog "^1.0.0" - interpret "^1.1.0" - isobject "^3.0.1" - liftoff "^3.1.0" - matchdep "^2.0.0" - mute-stdout "^1.0.0" - pretty-hrtime "^1.0.0" - replace-homedir "^1.0.0" - semver-greatest-satisfied-range "^1.1.0" - v8flags "^3.0.1" - yargs "^7.1.0" - -gulp-concat@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/gulp-concat/-/gulp-concat-2.6.1.tgz#633d16c95d88504628ad02665663cee5a4793353" - integrity sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M= - dependencies: - concat-with-sourcemaps "^1.0.0" - through2 "^2.0.0" - vinyl "^2.0.0" - -gulp-debug@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/gulp-debug/-/gulp-debug-4.0.0.tgz#036f9539c3fb6af720e01a9ea5c195fc73f29d5b" - integrity sha512-cn/GhMD2nVZCVxAl5vWao4/dcoZ8wUJ8w3oqTvQaGDmC1vT7swNOEbhQTWJp+/otKePT64aENcqAQXDcdj5H1g== - dependencies: - chalk "^2.3.0" - fancy-log "^1.3.2" - plur "^3.0.0" - stringify-object "^3.0.0" - through2 "^2.0.0" - tildify "^1.1.2" - -gulp@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.2.tgz#543651070fd0f6ab0a0650c6a3e6ff5a7cb09caa" - integrity sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA== - dependencies: - glob-watcher "^5.0.3" - gulp-cli "^2.2.0" - undertaker "^1.2.1" - vinyl-fs "^3.0.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - integrity sha1-4oxNRdBey77YGDY86PnFkmIp/+U= - dependencies: - glogg "^1.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-symbols@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" - integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -interpret@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" - integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== - -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -irregular-plurals@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-2.0.0.tgz#39d40f05b00f656d0b7fa471230dd3b714af2872" - integrity sha512-Y75zBYLkh0lJ9qxeHlMjQ7bSbyiSqNW/UOPWDmzC7cXskL1hekSITh1Oc6JV0XCWWZ9DE8VYSB71xocLk3gmGw== - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-negated-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" - integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI= - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== - -is-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= - -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" - integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - -is-utf8@^0.2.0, is-utf8@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-valid-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" - integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= - -just-debounce@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" - integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0, kind-of@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -last-run@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" - integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= - dependencies: - default-resolution "^2.0.0" - es6-weak-map "^2.0.1" - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ= - dependencies: - readable-stream "^2.0.5" - -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -lead@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" - integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI= - dependencies: - flush-write-stream "^1.0.2" - -liftoff@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" - integrity sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog== - dependencies: - extend "^3.0.0" - findup-sync "^3.0.0" - fined "^1.0.1" - flagged-respawn "^1.0.0" - is-plain-object "^2.0.4" - object.map "^1.0.0" - rechoir "^0.6.2" - resolve "^1.1.7" - -linkify-it@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.2.0.tgz#e3b54697e78bf915c70a38acd78fd09e0058b1cf" - integrity sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw== - dependencies: - uc.micro "^1.0.1" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - integrity sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -make-iterator@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" - integrity sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw== - dependencies: - kind-of "^6.0.2" - -map-cache@^0.2.0, map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -markdown-it@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-10.0.0.tgz#abfc64f141b1722d663402044e43927f1f50a8dc" - integrity sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg== - dependencies: - argparse "^1.0.7" - entities "~2.0.0" - linkify-it "^2.0.0" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -markdownlint@^0.20.2: - version "0.20.3" - resolved "https://registry.yarnpkg.com/markdownlint/-/markdownlint-0.20.3.tgz#6f56d3e16d990af79d42e58bd2849f70b3358595" - integrity sha512-J93s59tGvSFvAPWVUtEgxqPI0CHayTx1Z8poj1/4UJAquHGPIruWRMurkRldiNbgBiaQ4OOt15rHZbFfU6u05A== - dependencies: - markdown-it "10.0.0" - -matchdep@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e" - integrity sha1-xvNINKDY28OzfCfui7yyfHd1WC4= - dependencies: - findup-sync "^2.0.0" - micromatch "^3.0.4" - resolve "^1.4.0" - stack-trace "0.0.10" - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= - -micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -mute-stdout@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" - integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== - -nan@^2.12.1: - version "2.14.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" - integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -next-tick@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= - -normalize-package-data@^2.3.2: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -now-and-later@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c" - integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ== - dependencies: - once "^1.3.2" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-keys@^1.0.11, object-keys@^1.0.12: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.assign@^4.0.4, object.assign@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.defaults@^1.0.0, object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.map@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - integrity sha1-z4Plncj8wK1fQlDh94s7gb2AHTc= - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.pick@^1.2.0, object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -object.reduce@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" - integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -ordered-read-streams@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" - integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4= - dependencies: - readable-stream "^2.0.1" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - integrity sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= - dependencies: - error-ex "^1.2.0" - -parse-node-version@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" - integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - integrity sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= - dependencies: - pinkie-promise "^2.0.0" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0= - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc= - dependencies: - path-root-regex "^0.1.0" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - integrity sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE= - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -pify@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= - -plur@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/plur/-/plur-3.1.1.tgz#60267967866a8d811504fe58f2faaba237546a5b" - integrity sha512-t1Ax8KUvV3FFII8ltczPn2tJdjqbd1sIzu6t4JL7nQ3EyeL/lTrj5PWKb06ic5/6XYDr65rQ4uzQEGN70/6X5w== - dependencies: - irregular-plurals "^2.0.0" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -pretty-hrtime@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE= - -process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.5: - version "1.5.1" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" - integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== - dependencies: - duplexify "^3.6.0" - inherits "^2.0.3" - pump "^2.0.0" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - integrity sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI= - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - integrity sha1-9f+qXs0pyzHAR0vKfXVra7KePyg= - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -"readable-stream@2 || 3": - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= - dependencies: - resolve "^1.1.6" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -remove-bom-buffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" - integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ== - dependencies: - is-buffer "^1.1.5" - is-utf8 "^0.2.1" - -remove-bom-stream@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" - integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM= - dependencies: - remove-bom-buffer "^3.0.0" - safe-buffer "^5.1.0" - through2 "^2.0.3" - -remove-trailing-separator@^1.0.1, remove-trailing-separator@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" - integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -replace-ext@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" - integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== - -replace-homedir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" - integrity sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw= - dependencies: - homedir-polyfill "^1.0.1" - is-absolute "^1.0.0" - remove-trailing-separator "^1.1.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-options@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" - integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE= - dependencies: - value-or-function "^3.0.0" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.4.0: - version "1.17.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" - integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== - dependencies: - path-parse "^1.0.6" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -semver-greatest-satisfied-range@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" - integrity sha1-E+jCZYq5aRywzXEJMkAoDTb3els= - dependencies: - sver-compat "^1.5.0" - -"semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= - -source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -sparkles@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" - integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.5" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" - integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -stack-trace@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stream-exhaust@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" - integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -stringify-object@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" - integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== - dependencies: - get-own-enumerable-property-symbols "^3.0.0" - is-obj "^1.0.1" - is-regexp "^1.0.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= - dependencies: - is-utf8 "^0.2.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -sver-compat@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" - integrity sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg= - dependencies: - es6-iterator "^2.0.1" - es6-symbol "^3.1.1" - -through2-filter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254" - integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA== - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -through2@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" - integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== - dependencies: - readable-stream "2 || 3" - -tildify@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - integrity sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo= - dependencies: - os-homedir "^1.0.0" - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= - -to-absolute-glob@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" - integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs= - dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -to-through@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" - integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY= - dependencies: - through2 "^2.0.3" - -type@^1.0.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" - integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== - -type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" - integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= - -undertaker-registry@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" - integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= - -undertaker@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.1.tgz#701662ff8ce358715324dfd492a4f036055dfe4b" - integrity sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA== - dependencies: - arr-flatten "^1.0.1" - arr-map "^2.0.0" - bach "^1.0.0" - collection-map "^1.0.0" - es6-weak-map "^2.0.1" - last-run "^1.1.0" - object.defaults "^1.0.0" - object.reduce "^1.0.0" - undertaker-registry "^1.0.0" - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -unique-stream@^2.0.2: - version "2.3.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac" - integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A== - dependencies: - json-stable-stringify-without-jsonify "^1.0.1" - through2-filter "^3.0.0" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -v8flags@^3.0.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.3.tgz#fc9dc23521ca20c5433f81cc4eb9b3033bb105d8" - integrity sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w== - dependencies: - homedir-polyfill "^1.0.1" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -value-or-function@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" - integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM= - -vinyl-fs@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" - integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== - dependencies: - fs-mkdirp-stream "^1.0.0" - glob-stream "^6.1.0" - graceful-fs "^4.0.0" - is-valid-glob "^1.0.0" - lazystream "^1.0.0" - lead "^1.0.0" - object.assign "^4.0.4" - pumpify "^1.3.5" - readable-stream "^2.3.3" - remove-bom-buffer "^3.0.0" - remove-bom-stream "^1.2.0" - resolve-options "^1.1.0" - through2 "^2.0.0" - to-through "^2.0.0" - value-or-function "^3.0.0" - vinyl "^2.0.0" - vinyl-sourcemap "^1.1.0" - -vinyl-sourcemap@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" - integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY= - dependencies: - append-buffer "^1.0.2" - convert-source-map "^1.5.0" - graceful-fs "^4.1.6" - normalize-path "^2.1.1" - now-and-later "^2.0.0" - remove-bom-buffer "^3.0.0" - vinyl "^2.0.0" - -vinyl@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" - integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg== - dependencies: - clone "^2.1.1" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - -which@^1.2.14: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xtend@~4.0.0, xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -yargs-parser@5.0.0-security.0: - version "5.0.0-security.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0-security.0.tgz#4ff7271d25f90ac15643b86076a2ab499ec9ee24" - integrity sha512-T69y4Ps64LNesYxeYGYPvfoMTt/7y1XtfpIslUeK4um+9Hu7hlGoRtaDLvdXb7+/tfq4opVa2HRY5xGip022rQ== - dependencies: - camelcase "^3.0.0" - object.assign "^4.1.0" - -yargs@^7.1.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.1.tgz#67f0ef52e228d4ee0d6311acede8850f53464df6" - integrity sha512-huO4Fr1f9PmiJJdll5kwoS2e4GqzGSsMT3PPMpOwoVkOK8ckqAewMTZyA6LXVQWflleb/Z8oPBEvNsMft0XE+g== - dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "5.0.0-security.0" diff --git a/test/docker/networktest/DockerRemoting.Tests.ps1 b/test/docker/networktest/DockerRemoting.Tests.ps1 deleted file mode 100644 index 5f5a13c5b4c..00000000000 --- a/test/docker/networktest/DockerRemoting.Tests.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -$imageName = "remotetestimage" -Describe "Basic remoting test with docker" -tags @("Scenario","Slow"){ - BeforeAll { - $Timeout = 600 # 10 minutes to run these tests - $dockerimage = docker images --format "{{ .Repository }}" $imageName - if ( $dockerimage -ne $imageName ) { - $pending = $true - Write-Warning "Docker image '$imageName' not found, not running tests" - return - } - else { - $pending = $false - } - - # give the containers something to do, otherwise they will exit and be removed - Write-Verbose -Verbose "setting up docker container PowerShell server" - $server = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - Write-Verbose -Verbose "setting up docker container PowerShell client" - $client = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - - # get fullpath to installed core powershell - Write-Verbose -Verbose "Getting path to PowerShell" - $powershellcorepath = docker exec $server powershell -c "(get-childitem 'c:\program files\powershell\*\pwsh.exe').fullname" - if ( ! $powershellcorepath ) - { - $pending = $true - Write-Warning "Cannot find powershell executable, not running tests" - return - } - $powershellcoreversion = ($powershellcorepath -split "[\\/]")[-2] - # we will need the configuration of the core powershell endpoint - $powershellcoreConfiguration = "powershell.${powershellcoreversion}" - - # capture the hostnames of the containers which will be used by the tests - Write-Verbose -Verbose "getting server hostname" - $serverhostname = docker exec $server hostname - Write-Verbose -Verbose "getting client hostname" - $clienthostname = docker exec $client hostname - - # capture the versions of full and core PowerShell - Write-Verbose -Verbose "getting powershell full version" - $fullVersion = docker exec $client powershell -c "`$PSVersionTable.psversion.tostring()" - if ( ! $fullVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell full version, not running tests" - return - } - - Write-Verbose -Verbose "getting powershell version" - $coreVersion = docker exec $client "$powershellcorepath" -c "`$PSVersionTable.psversion.tostring()" - if ( ! $coreVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell version, not running tests" - return - } - } - - AfterAll { - # to debug, comment out the following - if ( $pending -eq $false ) { - docker rm -f $server - docker rm -f $client - } - } - - It "Full powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Full powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } - - It "Core powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Core powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } -} diff --git a/test/docker/networktest/Dockerfile b/test/docker/networktest/Dockerfile deleted file mode 100644 index 1e4f3d65ebb..00000000000 --- a/test/docker/networktest/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# escape=` -FROM microsoft/windowsservercore - -SHELL ["pwsh.exe","-command"] - -# the source msi should change on a daily basis -# the destination should not change -ADD PSCore.msi /PSCore.msi - -# the msipath (PSCore.msi) below will need to change based on the daily package -# set up for basic auth -# install-powershellremoting will restart winrm service -RUN new-LocalUser -Name testuser -password (ConvertTo-SecureString 11aa!!AA -asplaintext -force); ` - add-localgroupmember -group administrators -member testuser; ` - set-item wsman:/localhost/service/auth/basic $true; ` - set-item WSMan:/localhost/client/trustedhosts "*" -force; ` - set-item wsman:/localhost/service/AllowUnencrypted $true; ` - set-item WSMan:/localhost/client/AllowUnencrypted $true; ` - Start-Process -FilePath msiexec.exe -ArgumentList '-qn', ` - '-i c:\PSCore.msi','-log c:\PSCore-install.log','-norestart' -wait ; ` - $psexec = get-item -path ${ENV:ProgramFiles}/powershell/*/pwsh.exe; ` - $corehome = $psexec.directory.fullname; ` - & $psexec Install-PowerShellRemoting.ps1; ` - remove-item -force c:\PSCore.msi diff --git a/test/fuzzing/FuzzingApp/Program.cs b/test/fuzzing/FuzzingApp/Program.cs new file mode 100644 index 00000000000..4e309ffc468 --- /dev/null +++ b/test/fuzzing/FuzzingApp/Program.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using SharpFuzz; + +namespace FuzzTests +{ + public static class Program + { + public static void Main(string[] args) + { + FuzzTargetMethod(args); + } + + public static void FuzzTargetMethod(string[] args) + { + if (args == null) + { + Console.WriteLine("args was null"); + args = Array.Empty(); + } + + try + { + Fuzzer.LibFuzzer.Run(Target.ExtractToken); + } + catch (ArgumentNullException nex) + { + Console.WriteLine($"ArgumentNullException in main: {nex.Message}"); + Console.WriteLine($"Stack Trace: {nex.StackTrace}"); + Environment.Exit(1); + } + catch (Exception ex) + { + Console.WriteLine($"Exception in main: {ex.Message}"); + Console.WriteLine($"Exception type: {ex.GetType()}"); + Console.WriteLine($"Stack Trace: {ex.StackTrace}"); + Environment.Exit(1); + } + } + } +} diff --git a/test/fuzzing/FuzzingApp/Target.cs b/test/fuzzing/FuzzingApp/Target.cs new file mode 100644 index 00000000000..d82e17e8702 --- /dev/null +++ b/test/fuzzing/FuzzingApp/Target.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Remoting; + +namespace FuzzTests +{ + public static class Target + { + public static void ExtractToken(ReadOnlySpan tokenResponse) + { + RemoteSessionHyperVSocketClient.ExtractToken(tokenResponse); + } + } +} diff --git a/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj new file mode 100644 index 00000000000..7ee82c34cae --- /dev/null +++ b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj @@ -0,0 +1,25 @@ + + + + + + PowerShell Fuzzing + powershell-fuzz-tests + Exe + + + + true + ../../../src/signing/visualstudiopublic.snk + true + + + + + + + + + + + diff --git a/test/fuzzing/inputs/maxinput b/test/fuzzing/inputs/maxinput new file mode 100644 index 00000000000..6773a896b3c --- /dev/null +++ b/test/fuzzing/inputs/maxinput @@ -0,0 +1 @@ +TOKEN abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/= \ No newline at end of file diff --git a/test/fuzzing/inputs/mininput b/test/fuzzing/inputs/mininput new file mode 100644 index 00000000000..c9b91c737c2 --- /dev/null +++ b/test/fuzzing/inputs/mininput @@ -0,0 +1 @@ +TOKEN TQ \ No newline at end of file diff --git a/test/fuzzing/runFuzzer.ps1 b/test/fuzzing/runFuzzer.ps1 new file mode 100644 index 00000000000..5cb63393c4c --- /dev/null +++ b/test/fuzzing/runFuzzer.ps1 @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +1. libfuzzer-dotnet-windows.exe can be installed from https://github.com/Metalnem/libfuzzer-dotnet/releases + +2. sharpfuzz can be installed with dotnet-tool: + > dotnet tool install --global SharpFuzz.CommandLine --version 2.2.0 + +Usage: sharpfuzz [path-to-assembly] [prefix ...] + +path-to-assembly: + The path to an assembly .dll file to instrument. + +prefix: + The class or the namespace to instrument. + If not present, all types in the assembly will be instrumented. + At least one prefix is required when instrumenting System.Private.CoreLib. + +Examples: + sharpfuzz Newtonsoft.Json.dll + sharpfuzz System.Private.CoreLib.dll System.Number + sharpfuzz System.Private.CoreLib.dll System.DateTimeFormat System.DateTimeParse +#> + +param ( + [string]$libFuzzer = "$PSScriptRoot\libfuzzer-dotnet-windows.exe", + [string]$project = "$PSScriptRoot\FuzzingApp\powershell-fuzz-tests.csproj", + [string]$corpus = "$PSScriptRoot\inputs", + [string]$command = "sharpfuzz.exe" +) + +Set-StrictMode -Version Latest + +$outputDir = "$PSScriptRoot\out" + +if (Test-Path $outputDir) { + Remove-Item -Recurse -Force $outputDir +} + +Write-Host "dotnet publish $project -c Debug -o $outputDir" +dotnet publish $project -c Debug -o $outputDir +Write-Host "build completed" + +$projectName = (Get-Item $project).BaseName +$projectDll = "$projectName.dll" +$project = Join-Path $outputDir $projectDll +$smaDllPath = Join-Path $outputDir "System.Management.Automation.dll" + +## Instrument the specific class within the test assembly. +Write-Host "instrumenting: $project" +## !NOTE! If you instrument the class that defines "Main", it will fail. +& $command $project "FuzzTests.Target" +Write-Host "done instrumenting $project" + +## Instrument any other assemblies that need to be tested. +Write-Host "instrumenting: $smaDllPath" +& $command $smaDllPath "System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient" +Write-Host "done instrumenting: $smaDllPath" + +$outputPath = Join-Path $outputDir "output.txt" + +Write-Host "launching fuzzer on $project" +Write-Host "$libFuzzer --target_path=dotnet --target_arg=$project $corpus" +& $libFuzzer --target_path=dotnet --target_arg=$project $corpus -max_len=1024 2>&1 | Tee-Object -FilePath $outputPath diff --git a/test/hosting/NuGet.Config b/test/hosting/NuGet.Config deleted file mode 100644 index c2c6e1b7c2d..00000000000 --- a/test/hosting/NuGet.Config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/test/hosting/hosting.tests.csproj b/test/hosting/hosting.tests.csproj index 0e459076c28..1c40094c7ff 100644 --- a/test/hosting/hosting.tests.csproj +++ b/test/hosting/hosting.tests.csproj @@ -5,7 +5,6 @@ PowerShell hosting SDK xUnit Tests powershell-hosting-tests - @@ -15,12 +14,19 @@ - + - + + + + + + + + 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/infrastructure/ciModule.Tests.ps1 b/test/infrastructure/ciModule.Tests.ps1 new file mode 100644 index 00000000000..b7320ff49b7 --- /dev/null +++ b/test/infrastructure/ciModule.Tests.ps1 @@ -0,0 +1,246 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# NOTE: This test file tests the Test-MergeConflictMarker function which detects Git merge conflict markers. +# IMPORTANT: Do NOT use here-strings or literal conflict markers (e.g., "<<<<<<<", "=======", ">>>>>>>") +# in this file, as they will trigger conflict marker detection in CI pipelines. +# Instead, use string multiplication (e.g., '<' * 7) to dynamically generate these markers at runtime. + +Describe "Test-MergeConflictMarker" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + + # Create a temporary test workspace + $script:testWorkspace = Join-Path $TestDrive "workspace" + New-Item -ItemType Directory -Path $script:testWorkspace -Force | Out-Null + + # Create temporary output files + $script:testOutputPath = Join-Path $TestDrive "outputs.txt" + $script:testSummaryPath = Join-Path $TestDrive "summary.md" + } + + AfterEach { + # Clean up test files after each test + if (Test-Path $script:testWorkspace) { + Get-ChildItem $script:testWorkspace -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + } + Remove-Item $script:testOutputPath -Force -ErrorAction SilentlyContinue + Remove-Item $script:testSummaryPath -Force -ErrorAction SilentlyContinue + } + + Context "When no files are provided" { + It "Should handle empty file array gracefully" { + # The function now accepts empty arrays to handle cases like delete-only PRs + $emptyArray = @() + Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Files to Check" + } + } + + Context "When files have no conflicts" { + It "Should pass for clean files" { + $testFile = Join-Path $script:testWorkspace "clean.txt" + "This is a clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("clean.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Conflicts Found" + } + } + + Context "When files have conflict markers" { + It "Should detect <<<<<<< marker" { + $testFile = Join-Path $script:testWorkspace "conflict1.txt" + "Some content`n" + ('<' * 7) + " HEAD`nConflicting content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict1.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=1" + } + + It "Should detect ======= marker" { + $testFile = Join-Path $script:testWorkspace "conflict2.txt" + "Some content`n" + ('=' * 7) + "`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict2.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect >>>>>>> marker" { + $testFile = Join-Path $script:testWorkspace "conflict3.txt" + "Some content`n" + ('>' * 7) + " branch-name`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict3.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect multiple markers in one file" { + $testFile = Join-Path $script:testWorkspace "conflict4.txt" + $content = "Some content`n" + ('<' * 7) + " HEAD`nContent A`n" + ('=' * 7) + "`nContent B`n" + ('>' * 7) + " branch`nMore content" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict4.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "Conflicts Detected" + $summary | Should -Match "conflict4.txt" + } + + It "Should detect conflicts in multiple files" { + $testFile1 = Join-Path $script:testWorkspace "conflict5.txt" + ('<' * 7) + " HEAD" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "conflict6.txt" + ('=' * 7) | Out-File $testFile2 -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict5.txt", "conflict6.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=2" + } + } + + Context "When markers are not at line start" { + It "Should not detect markers in middle of line" { + $testFile = Join-Path $script:testWorkspace "notconflict.txt" + "This line has <<<<<<< in the middle" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("notconflict.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should not detect markers with wrong number of characters" { + $testFile = Join-Path $script:testWorkspace "wrongcount.txt" + ('<' * 6) + " Only 6`n" + ('<' * 8) + " 8 characters" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("wrongcount.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When handling special file scenarios" { + It "Should skip non-existent files" { + Test-MergeConflictMarker -File @("nonexistent.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + } + + It "Should handle absolute paths" { + $testFile = Join-Path $script:testWorkspace "absolute.txt" + "Clean content" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @($testFile) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should handle mixed relative and absolute paths" { + $testFile1 = Join-Path $script:testWorkspace "relative.txt" + "Clean" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "absolute.txt" + "Clean" | Out-File $testFile2 -Encoding utf8 + + Test-MergeConflictMarker -File @("relative.txt", $testFile2) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When summary and output generation" { + It "Should generate proper GitHub Actions outputs format" { + $testFile = Join-Path $script:testWorkspace "test.txt" + "Clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("test.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Where-Object {$_ -match "^files-checked=\d+$"} | Should -Not -BeNullOrEmpty + $outputs | Where-Object {$_ -match "^conflicts-found=\d+$"} | Should -Not -BeNullOrEmpty + } + + It "Should generate markdown summary with conflict details" { + $testFile = Join-Path $script:testWorkspace "marked.txt" + $content = "Line 1`n" + ('<' * 7) + " HEAD`nLine 3`n" + ('=' * 7) + "`nLine 5" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("marked.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "# Merge Conflict Marker Check Results" + $summary | Should -Match "marked.txt" + $summary | Should -Match "\| Line \| Marker \|" + } + } +} + +Describe "Install-CIPester" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + } + + Context "When checking function exists" { + It "Should export Install-CIPester function" { + $function = Get-Command Install-CIPester -ErrorAction SilentlyContinue + $function | Should -Not -BeNullOrEmpty + $function.ModuleName | Should -Be 'ci' + } + + It "Should have expected parameters" { + $function = Get-Command Install-CIPester + $function.Parameters.Keys | Should -Contain 'MinimumVersion' + $function.Parameters.Keys | Should -Contain 'MaximumVersion' + $function.Parameters.Keys | Should -Contain 'Force' + } + + It "Should accept version parameters" { + $function = Get-Command Install-CIPester + $function.Parameters['MinimumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['MaximumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['Force'].ParameterType.Name | Should -Be 'SwitchParameter' + } + } + + Context "When validating real execution" { + # These tests only run in CI where we can safely install/test Pester + + It "Should successfully run without errors when Pester exists" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -ErrorAction Stop } | Should -Not -Throw + } + + It "Should accept custom version parameters" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion '5.99.99' -ErrorAction Stop } | Should -Not -Throw + } + } +} + diff --git a/test/packaging/linux/package-validation.tests.ps1 b/test/packaging/linux/package-validation.tests.ps1 new file mode 100644 index 00000000000..3b961120f2f --- /dev/null +++ b/test/packaging/linux/package-validation.tests.ps1 @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Linux Package Name Validation" { + BeforeAll { + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "$env:GITHUB_WORKSPACE/../packages" + } else { + $env:SYSTEM_ARTIFACTSDIRECTORY + } + + if (-not $artifactsDir) { + throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set." + } + + Write-Verbose "Artifacts directory: $artifactsDir" -Verbose + } + + Context "RPM Package Names" { + It "Should have valid RPM package names" { + $rpmPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.rpm -ErrorAction SilentlyContinue + + $rpmPackages.Count | Should -BeGreaterThan 0 -Because "At least one RPM package should exist in the artifacts directory" + + $invalidPackages = @() + # Regex pattern for valid RPM package names. + # Breakdown: + # ^powershell\- : Starts with 'powershell-' + # (preview-|lts-)? : Optionally 'preview-' or 'lts-' + # \d+\.\d+\.\d+ : Version number (e.g., 7.6.0) + # (_[a-z]*\.\d+)? : Optional underscore, letters, dot, and digits (e.g., _alpha.1) + # -1\. : Literal '-1.' + # (preview\.\d+\.)? : Optional 'preview.' and digits, followed by a dot + # (rh|cm)\. : Either 'rh.' or 'cm.' + # (x86_64|aarch64)\.rpm$ : Architecture and file extension + $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(preview\.\d+\.)?(rh|cm)\.(x86_64|aarch64)\.rpm' + + foreach ($package in $rpmPackages) { + if ($package.Name -notmatch $rpmPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid RPM package name" + Write-Warning "$($package.Name) is not a valid RPM package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "DEB Package Names" { + It "Should have valid DEB package names" { + $debPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.deb -ErrorAction SilentlyContinue + + $debPackages.Count | Should -BeGreaterThan 0 -Because "At least one DEB package should exist in the artifacts directory" + + $invalidPackages = @() + # Regex pattern for valid DEB package names. + # Valid examples: + # - powershell-preview_7.6.0-preview.6-1.deb_amd64.deb + # - powershell-lts_7.4.13-1.deb_amd64.deb + # - powershell_7.4.13-1.deb_amd64.deb + # - powershell_7.6.0-1.deb_arm64.deb + # Breakdown: + # ^powershell : Starts with 'powershell' + # (-preview|-lts)? : Optionally '-preview' or '-lts' + # _\d+\.\d+\.\d+ : Underscore followed by version number (e.g., _7.6.0) + # (-[a-z]+\.\d+)? : Optional dash, letters, dot, and digits (e.g., -preview.6) + # -1 : Literal '-1' + # \.deb_ : Literal '.deb_' + # (amd64|arm64) : Architecture + # \.deb$ : File extension + $debPackageNamePattern = '^powershell(-preview|-lts)?_\d+\.\d+\.\d+(-[a-z]+\.\d+)?-1\.deb_(amd64|arm64)\.deb$' + + foreach ($package in $debPackages) { + if ($package.Name -notmatch $debPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid DEB package name" + Write-Warning "$($package.Name) is not a valid DEB package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "Tar.Gz Package Names" { + It "Should have valid tar.gz package names" { + $tarPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.tar.gz -ErrorAction SilentlyContinue + + $tarPackages.Count | Should -BeGreaterThan 0 -Because "At least one tar.gz package should exist in the artifacts directory" + + $invalidPackages = @() + foreach ($package in $tarPackages) { + # Pattern matches: powershell-7.6.0-preview.6-linux-x64.tar.gz or powershell-7.6.0-linux-x64.tar.gz + # Also matches various runtime configurations + if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { + $invalidPackages += "$($package.Name) is not a valid tar.gz package name" + Write-Warning "$($package.Name) is not a valid tar.gz package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "Package Existence" { + It "Should find at least one package in artifacts directory" { + $allPackages = Get-ChildItem -Path $artifactsDir -Recurse -Include *.rpm, *.tar.gz, *.deb -ErrorAction SilentlyContinue + + $allPackages.Count | Should -BeGreaterThan 0 -Because "At least one package should exist in the artifacts directory" + } + } +} diff --git a/test/packaging/macos/package-validation.tests.ps1 b/test/packaging/macos/package-validation.tests.ps1 new file mode 100644 index 00000000000..945ffea6f7a --- /dev/null +++ b/test/packaging/macos/package-validation.tests.ps1 @@ -0,0 +1,186 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Verify macOS Package" { + BeforeAll { + Write-Verbose "In Describe BeforeAll" -Verbose + Import-Module $PSScriptRoot/../../../build.psm1 + + # Find the macOS package + $packagePath = $env:PACKAGE_FOLDER + if (-not $packagePath) { + $packagePath = Get-Location + } + + Write-Verbose "Looking for package in: $packagePath" -Verbose + $package = Get-ChildItem -Path $packagePath -Filter "*.pkg" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if (-not $package) { + Write-Warning "No .pkg file found in $packagePath" + } else { + Write-Verbose "Found package: $($package.FullName)" -Verbose + } + + # Set up test directories + $script:package = $package + $script:expandDir = $null + $script:payloadDir = $null + $script:extractedFiles = @() + + if ($package) { + # Use TestDrive for temporary directories - pkgutil will create the expand directory + $script:expandDir = Join-Path "TestDrive:" -ChildPath "package-contents-test" + $expandDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:expandDir = Join-Path $expandDirResolved -ChildPath "package-contents-test" + + Write-Verbose "Expanding package to: $($script:expandDir)" -Verbose + # pkgutil will create the directory itself, so don't pre-create it + Start-NativeExecution { + pkgutil --expand $package.FullName $script:expandDir + } + + # Extract the payload to verify files + $script:payloadDir = Join-Path "TestDrive:" -ChildPath "package-payload-test" + $payloadDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:payloadDir = Join-Path $payloadDirResolved -ChildPath "package-payload-test" + + # Create payload directory since cpio needs it + if (-not (Test-Path $script:payloadDir)) { + $null = New-Item -ItemType Directory -Path $script:payloadDir -Force + } + + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse | Select-Object -First 1 + if ($componentPkg) { + Write-Verbose "Extracting payload from: $($componentPkg.FullName)" -Verbose + Push-Location $script:payloadDir + try { + $payloadFile = Join-Path $componentPkg.FullName "Payload" + Get-Content -Path $payloadFile -Raw -AsByteStream | & cpio -i 2>&1 | Out-Null + } finally { + Pop-Location + } + } + + # Get all extracted files for verification + $script:extractedFiles = Get-ChildItem -Path $script:payloadDir -Recurse -ErrorAction SilentlyContinue + Write-Verbose "Extracted $($script:extractedFiles.Count) files" -Verbose + } + } + + AfterAll { + # TestDrive automatically cleans up, but we can ensure cleanup happens + # No manual cleanup needed as TestDrive handles it + } + + Context "Package existence and structure" { + It "Package file should exist" { + $script:package | Should -Not -BeNullOrEmpty -Because "A .pkg file should be created" + $script:package.Extension | Should -Be ".pkg" + } + + It "Package name should follow correct naming convention" { + $script:package | Should -Not -BeNullOrEmpty + + # Regex pattern for valid macOS PKG package names. + # This pattern matches the validation used in release-validate-packagenames.yml + # Valid examples: + # - powershell-7.4.13-osx-x64.pkg (Stable release) + # - powershell-7.6.0-preview.6-osx-x64.pkg (Preview version string) + # - powershell-7.4.13-rebuild.5-osx-arm64.pkg (Rebuild version) + # - powershell-lts-7.4.13-osx-arm64.pkg (LTS package) + $pkgPackageNamePattern = '^powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx\-(x64|arm64)\.pkg$' + + $script:package.Name | Should -Match $pkgPackageNamePattern -Because "Package name should follow the standard naming convention" + } + + It "Package name should NOT use x86_64 with underscores" { + $script:package | Should -Not -BeNullOrEmpty + + $script:package.Name | Should -Not -Match 'x86_64' -Because "Package should use 'x64' not 'x86_64' (with underscores) for compatibility" + } + + It "Package should expand successfully" { + $script:expandDir | Should -Exist + Get-ChildItem -Path $script:expandDir | Should -Not -BeNullOrEmpty + } + + It "Package should have a component package" { + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse -ErrorAction SilentlyContinue + $componentPkg | Should -Not -BeNullOrEmpty -Because "Package should contain a component.pkg" + } + + It "Payload should extract successfully" { + $script:payloadDir | Should -Exist + $script:extractedFiles | Should -Not -BeNullOrEmpty -Because "Package payload should contain files" + } + } + + Context "Required files in package" { + BeforeAll { + $expectedFilePatterns = @{ + "PowerShell executable" = "usr/local/microsoft/powershell/*/pwsh" + "PowerShell symlink in /usr/local/bin" = "usr/local/bin/pwsh*" + "Man page" = "usr/local/share/man/man1/pwsh*.gz" + "Launcher application plist" = "Applications/PowerShell*.app/Contents/Info.plist" + } + + $testCases = @() + foreach ($key in $expectedFilePatterns.Keys) { + $testCases += @{ + Description = $key + Pattern = $expectedFilePatterns[$key] + } + } + + $script:testCases = $testCases + } + + It "Should contain " -TestCases $script:testCases { + param($Description, $Pattern) + + $found = $script:extractedFiles | Where-Object { $_.FullName -like "*$Pattern*" } + $found | Should -Not -BeNullOrEmpty -Because "$Description should exist in the package at path matching '$Pattern'" + } + } + + Context "PowerShell binary verification" { + It "PowerShell executable should be executable" { + $pwshBinary = $script:extractedFiles | Where-Object { $_.FullName -like "*/pwsh" -and $_.FullName -like "*/microsoft/powershell/*" } + $pwshBinary | Should -Not -BeNullOrEmpty + + # Check if file has executable permissions (on Unix-like systems) + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $pwshBinary[0].FullName).UnixFileMode + # Executable bit should be set + $permissions.ToString() | Should -Match 'x' -Because "pwsh binary should have execute permissions" + } + } + } + + Context "Launcher application" { + It "Launcher app should have proper bundle structure" { + $plistFile = $script:extractedFiles | Where-Object { $_.FullName -like "*PowerShell*.app/Contents/Info.plist" } + $plistFile | Should -Not -BeNullOrEmpty + + # Verify the bundle has required components + $appPath = Split-Path (Split-Path $plistFile[0].FullName -Parent) -Parent + $macOSDir = Join-Path $appPath "Contents/MacOS" + $resourcesDir = Join-Path $appPath "Contents/Resources" + + Test-Path $macOSDir | Should -Be $true -Because "App bundle should have Contents/MacOS directory" + Test-Path $resourcesDir | Should -Be $true -Because "App bundle should have Contents/Resources directory" + } + + It "Launcher script should exist and be executable" { + $launcherScript = $script:extractedFiles | Where-Object { + $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" + } + $launcherScript | Should -Not -BeNullOrEmpty -Because "Launcher script should exist" + + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $launcherScript[0].FullName).UnixFileMode + $permissions.ToString() | Should -Match 'x' -Because "Launcher script should have execute permissions" + } + } + } +} diff --git a/test/packaging/packaging.tests.ps1 b/test/packaging/packaging.tests.ps1 new file mode 100644 index 00000000000..a7d322205bc --- /dev/null +++ b/test/packaging/packaging.tests.ps1 @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Packaging Module Functions" { + BeforeAll { + Import-Module $PSScriptRoot/../../build.psm1 -Force + Import-Module $PSScriptRoot/../../tools/packaging/packaging.psm1 -Force + } + + Context "Test-IsPreview function" { + It "Should return True for preview versions" { + Test-IsPreview -Version "7.6.0-preview.6" | Should -Be $true + Test-IsPreview -Version "7.5.0-rc.1" | Should -Be $true + } + + It "Should return False for stable versions" { + Test-IsPreview -Version "7.6.0" | Should -Be $false + Test-IsPreview -Version "7.5.0" | Should -Be $false + } + + It "Should return False for LTS builds regardless of version string" { + Test-IsPreview -Version "7.6.0-preview.6" -IsLTS | Should -Be $false + Test-IsPreview -Version "7.5.0" -IsLTS | Should -Be $false + } + } + + Context "Get-MacOSPackageIdentifierInfo function (New-MacOSPackage logic)" { + It "Should detect preview builds and return preview identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0-preview.6" -LTS:$false + + $result.IsPreview | Should -Be $true + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + + It "Should detect stable builds and return stable identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0" -LTS:$false + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should treat LTS builds as stable even with preview version string" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.4.0-preview.1" -LTS:$true + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should NOT use package name for preview detection (bug fix verification) - " -TestCases @( + @{ Version = "7.6.0-preview.6"; Name = "Preview" } + @{ Version = "7.6.0-rc.1"; Name = "RC" } + ) { + # This test verifies the fix for issue #26673 + # The bug was using ($Name -like '*-preview') which always returned false + # because preview builds use Name="powershell" not "powershell-preview" + param($Version) + + # The CORRECT logic (the fix): uses version string + $result = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$false + $result.IsPreview | Should -Be $true -Because "Version string correctly identifies preview" + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + } +} diff --git a/test/packaging/windows/exe.tests.ps1 b/test/packaging/windows/exe.tests.ps1 new file mode 100644 index 00000000000..5a72195900e --- /dev/null +++ b/test/packaging/windows/exe.tests.ps1 @@ -0,0 +1,126 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe -Name "Windows EXE" -Fixture { + BeforeAll { + function Test-Elevated { + [CmdletBinding()] + [OutputType([bool])] + Param() + + # if the current Powershell session was called with administrator privileges, + # the Administrator Group's well-known SID will show up in the Groups for the current identity. + # Note that the SID won't show up unless the process is elevated. + return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544") + } + + function Invoke-ExeInstaller { + param( + [Parameter(ParameterSetName = 'Install', Mandatory)] + [Switch]$Install, + + [Parameter(ParameterSetName = 'Uninstall', Mandatory)] + [Switch]$Uninstall, + + [Parameter(Mandatory)] + [ValidateScript({Test-Path -Path $_})] + [String]$ExePath + ) + $action = "$($PSCmdlet.ParameterSetName)ing" + if ($Install) { + $switch = '/install' + } else { + $switch = '/uninstall' + } + + $installProcess = Start-Process -wait $ExePath -ArgumentList $switch, '/quiet', '/norestart' -PassThru + if ($installProcess.ExitCode -ne 0) { + $exitCode = $installProcess.ExitCode + throw "$action EXE failed and returned error code $exitCode." + } + } + + $exePath = $env:PsExePath + $channel = $env:PSMsiChannel + $runtime = $env:PSMsiRuntime + + # Get any existing powershell in the path + $beforePath = @(([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object {$_ -like '*files\powershell*'}) + + foreach ($pathPart in $beforePath) { + Write-Warning "Found existing PowerShell path: $pathPart" + } + + if (!(Test-Elevated)) { + Write-Warning "Tests must be elevated" + } + } + BeforeEach { + $error.Clear() + } + + Context "$Channel-$Runtime" { + BeforeAll { + Write-Verbose "cr-$channel-$runtime" -Verbose + switch ("$channel-$runtime") { + "preview-win7-x64" { + $msiUpgradeCode = '39243d76-adaf-42b1-94fb-16ecf83237c8' + } + "stable-win7-x64" { + $msiUpgradeCode = '31ab5147-9a97-4452-8443-d9709f0516e1' + } + "preview-win7-x86" { + $msiUpgradeCode = '86abcfbd-1ccc-4a88-b8b2-0facfde29094' + } + "stable-win7-x86" { + $msiUpgradeCode = '1d00683b-0f84-4db8-a64f-2f98ad42fe06' + } + default { + throw "'$_' not a valid channel runtime combination" + } + } + } + + It "$Channel MSI should not be installed before test" -Skip:(!(Test-Elevated)) { + $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'") + $result.Count | Should -Be 0 -Because "Query should return nothing if $channel $runtime is not installed" + } + + It "EXE should install without error" -Skip:(!(Test-Elevated)) { + { + Invoke-ExeInstaller -Install -ExePath $exePath + } | Should -Not -Throw + } + + It "Upgrade code should be correct" -Skip:(!(Test-Elevated)) { + $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'") + $result.Count | Should -Be 1 -Because "Query should return 1 result if Upgrade code is for $runtime $channel" + } + + It "MSI should have updated path" -Skip:(!(Test-Elevated)) { + if ($channel -eq 'preview') { + $pattern = '*files*\powershell*\preview*' + } else { + $pattern = '*files*\powershell*' + } + + $psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object { $_ -like $pattern -and $_ -notin $beforePath } + + if (!$psPath) { + ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object { $_ -notin $beforePath } | + ForEach-Object { Write-Verbose -Verbose $_ } + } + + $psPath | Should -Not -BeNullOrEmpty + } + + It "MSI should uninstall without error" -Skip:(!(Test-Elevated)) { + { + Invoke-ExeInstaller -Uninstall -ExePath $exePath + } | Should -Not -Throw + } + } +} diff --git a/test/packaging/windows/msi.tests.ps1 b/test/packaging/windows/msi.tests.ps1 index d9541b74f06..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 @@ -53,6 +111,30 @@ 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 ';' | @@ -69,25 +151,63 @@ 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 { - $previewUpgladeCode = '39243d76-adaf-42b1-94fb-16ecf83237c8' + 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" + } + } } - It "Preview MSI should not be installed before test" -Skip:(!(Test-Elevated)) { - $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$previewUpgladeCode}'") - $result.Count | Should -Be 0 -Because 'Query should return nothing if preview x64 is not installed' + It "$Channel MSI should not be installed before test" -Skip:(!(Test-Elevated)) { + $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'") + $result.Count | Should -Be 0 -Because "Query should return nothing if $channel $runtime is not installed" } It "MSI should install without error" -Skip:(!(Test-Elevated)) { @@ -97,8 +217,37 @@ Describe -Name "Windows MSI" -Fixture { } It "Upgrade code should be correct" -Skip:(!(Test-Elevated)) { - $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$previewUpgladeCode}'") - $result.Count | Should -Be 1 -Because 'Query should return 1 result if Upgrade code is for x64 preview' + $result = @(Get-CimInstance -Query "SELECT Value FROM Win32_Property WHERE Property='UpgradeCode' and Value = '{$msiUpgradeCode}'") + $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)) { @@ -109,19 +258,65 @@ 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 } It "MSI should have not be updated path" -Skip:(!(Test-Elevated)) { $psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | - Where-Object {$_ -like '*files\powershell*' -and $_ -notin $beforePath} + Where-Object { $_ -like '*files\powershell*' -and $_ -notin $beforePath } $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 @@ -137,8 +332,20 @@ Describe -Name "Windows MSI" -Fixture { } It "MSI should have updated path" -Skip:(!(Test-Elevated)) { + if ($channel -eq 'preview') { + $pattern = '*files*\powershell*\preview*' + } else { + $pattern = '*files*\powershell*' + } + $psPath = ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | - Where-Object {$_ -like '*files\powershell*\preview*' -and $_ -notin $beforePath} + Where-Object { $_ -like $pattern -and $_ -notin $beforePath } + + if (!$psPath) { + ([System.Environment]::GetEnvironmentVariable('PATH', 'MACHINE')) -split ';' | + Where-Object { $_ -notin $beforePath } | + ForEach-Object { Write-Verbose -Verbose $_ } + } $psPath | Should -Not -BeNullOrEmpty } @@ -148,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..68385847f5b --- /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..be731373036 --- /dev/null +++ b/test/perf/benchmarks/assets/compiler.test.ps1 @@ -0,0 +1,2665 @@ +## 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 + } + } + } + "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..92c3f13d290 --- /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..48856632317 --- /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..9bc477bc4fe --- /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; + } + } +} 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..6f84b3f5767 --- /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.$)]") + ); + } +} 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..d7089f6f5a8 --- /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 + } +} 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..cd9c3a424ce --- /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())); + } + } +} 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..0309e1e9065 --- /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..85f5d98af59 --- /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); + } + } +} 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..f97f0771b99 --- /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; } + } +} 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..760142d3137 --- /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; } + } +} diff --git a/test/perf/dotnet-tools/Reporting/Reporter.cs b/test/perf/dotnet-tools/Reporting/Reporter.cs new file mode 100644 index 00000000000..d99ecfaf47c --- /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..b11b5e36ec4 --- /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..90a439f66cc --- /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%" }); + } + } + } +} diff --git a/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs b/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs new file mode 100644 index 00000000000..94511488efd --- /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; } + } +} diff --git a/test/perf/dotnet-tools/ResultsComparer/Program.cs b/test/perf/dotnet-tools/ResultsComparer/Program.cs new file mode 100644 index 00000000000..a0c14e0057a --- /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..ff3f14d5a0d --- /dev/null +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -0,0 +1,15 @@ + + + Exe + $(PERFLAB_TARGET_FRAMEWORKS) + net5.0 + preview + + + + + + + + + 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 d8eec255184..534cce2d356 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' } @@ -100,19 +105,6 @@ Describe "ConsoleHost unit tests" -tags "Feature" { { & $powershell -outp blah -comm { $input } } | Should -Throw -ErrorId "IncorrectValueForFormatParameter" } - It "Verify Validate Dollar Error Populated should throw exception" { - $origEA = $ErrorActionPreference - $ErrorActionPreference = "Stop" - $a = 1,2,3 - $e = { - $a | & $powershell -noprofile -command { wgwg-wrwrhqwrhrh35h3h3} - } | Should -Throw -ErrorId "CommandNotFoundException" -PassThru - - $e.ToString() | Should -Match "wgwg-wrwrhqwrhrh35h3h3" - - $ErrorActionPreference = $origEA - } - It "Verify Validate Output Format As Text Explicitly Child Single Shell does not throw" { { "blahblah" | & $powershell -noprofile -out text -com { $input } @@ -160,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 @@ -221,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 @@ -248,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 @@ -283,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 } @@ -381,8 +386,48 @@ export $envVarName='$guid' } } + Context "-SettingsFile Commandline switch set 'PSModulePath'" { + + BeforeAll { + $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'powershell.test.json' + $mPath1 = Join-Path $PSHOME 'Modules' + $mPath2 = Join-Path $TestDrive 'NonExist' + $pathSep = [System.IO.Path]::PathSeparator + + ## Use multiple paths in the setting. + $ModulePath = "${mPath1}${pathSep}${mPath2}".Replace('\', "\\") + Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"Unrestricted`", `"PSModulePath`": `"$ModulePath`" }" -ErrorAction Stop + } + + It "Verify PowerShell PSModulePath should contain paths from config file" { + $psModulePath = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command '$env:PSModulePath' + + ## $mPath1 already exists in the value of env PSModulePath, so it won't be added again. + $index = $psModulePath.IndexOf("${mPath1}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) + $index | Should -BeGreaterThan 0 + $index += $mPath1.Length + $psModulePath.IndexOf($mPath1, $index, [System.StringComparison]::OrdinalIgnoreCase) | Should -BeExactly -1 + + ## $mPath2 should be added at the index position 0. + $psModulePath.StartsWith("${mPath2}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) | Should -BeTrue + } + } + Context "Pipe to/from powershell" { - $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 @@ -401,20 +446,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 } } @@ -478,7 +545,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. @@ -573,6 +648,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") @@ -651,6 +728,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" { @@ -659,7 +750,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 } } @@ -820,6 +911,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 { @@ -835,7 +1012,12 @@ public static WINDOWPLACEMENT GetPlacement(IntPtr hwnd) { WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); placement.length = Marshal.SizeOf(placement); - GetWindowPlacement(hwnd, ref placement); + + if (!GetWindowPlacement(hwnd, ref placement)) + { + throw new System.ComponentModel.Win32Exception(); + } + return placement; } @@ -871,13 +1053,17 @@ public enum ShowWindowCommands : int $global:PSDefaultParameterValues = $defaultParamValues } - It "-WindowStyle should work on Windows" -TestCases @( + It "-WindowStyle should work on Windows" -Pending -TestCases @( @{WindowStyle="Normal"}, @{WindowStyle="Minimized"}, @{WindowStyle="Maximized"} # hidden doesn't work in CI/Server Core ) { 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 @@ -1011,8 +1197,57 @@ 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 + } + } + + It 'No_COLOR should be respected for redirected output' { + $psi = [System.Diagnostics.ProcessStartInfo] @{ + FileName = 'pwsh' + # Pass a command that succeeds and normally produces colored output, and one that produces error output. + Arguments = '-NoProfile -Command Get-Item .; Get-Content \nosuch123' + # Redirect (capture) both stdout and stderr. + RedirectStandardOutput = $true + RedirectStandardError = $true + } + $psi.Environment.Add('NO_COLOR', 1) + ($ps = [System.Diagnostics.Process]::Start($psi)).WaitForExit() + $ps.StandardOutput.ReadToEnd() | Should -Not -Contain '\e' + $ps.StandardError.ReadToEnd() | Should -Not -Contain '\e' + } +} diff --git a/test/powershell/Host/HostUtilities.Tests.ps1 b/test/powershell/Host/HostUtilities.Tests.ps1 index 6c791b5c446..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!"') @@ -58,3 +61,38 @@ Describe "InvokeOnRunspace method on remote runspace" -tags "Feature","RequireAd $results[0] | Should -Be "Hello!" } } + +Describe 'PromptForCredential' -Tags "CI" { + BeforeAll { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('NoPromptForPassword', $true) + } + + AfterAll { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('NoPromptForPassword', $false) + } + + It 'Should accept no targetname' { + $out = $Host.UI.PromptForCredential('caption','message','myUser',$null) + $out.UserName | Should -BeExactly 'myUser' + } + + It 'Should accept targetname as domain' { + $out = $Host.UI.PromptForCredential('caption','message','myUser','myDomain') + $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 2ef9fb30a23..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" - $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 deleted file mode 100644 index 35f86459861..00000000000 --- a/test/powershell/Host/ScreenReader.Tests.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Describe "Validate start of console host" -Tag CI { - BeforeAll { - if (-not $IsWindows) { - return - } - - $csharp_source = @' - using System; - using System.Runtime.InteropServices; - - public class ScreenReaderTestUtility { - private const uint SPI_SETSCREENREADER = 0x0047; - - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni); - - public static bool ActivateScreenReader() { - return SystemParametersInfo(SPI_SETSCREENREADER, 1u, IntPtr.Zero, 0); - } - - public static bool DeactivateScreenReader() { - return SystemParametersInfo(SPI_SETSCREENREADER, 0u, IntPtr.Zero, 0); - } - } -'@ - $utilType = "ScreenReaderTestUtility" -as [type] - if (-not $utilType) { - $utilType = Add-Type -TypeDefinition $csharp_source -PassThru - } - - ## Make the screen reader status active. - $utilType::ActivateScreenReader() - } - - AfterAll { - if ($IsWindows) { - ## Make the screen reader status in-active. - $utilType::DeactivateScreenReader() - } - } - - It "PSReadLine should not be auto-loaded when screen reader status is active" -Skip:(-not $IsWindows) { - $output = & "$PSHOME/pwsh" -noprofile -noexit -c "Get-Module PSReadLine; exit" - $output.Length | Should -BeExactly 2 - - ## The warning message about screen reader should be returned, but the PSReadLine module should not be loaded. - $output[0] | Should -BeLike "Warning:*'Import-Module PSReadLine'." - $output[1] | Should -BeExactly ([string]::Empty) - } -} diff --git a/test/powershell/Host/Startup.Tests.ps1 b/test/powershell/Host/Startup.Tests.ps1 index 05b2534bc9a..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,19 +14,16 @@ 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' 'System.ComponentModel.TypeConverter.dll' 'System.Console.dll' 'System.Data.Common.dll' - 'System.Diagnostics.FileVersionInfo.dll' 'System.Diagnostics.Process.dll' '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' @@ -37,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' @@ -46,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' @@ -70,16 +66,13 @@ Describe "Validate start of console host" -Tag CI { 'System.DirectoryServices.dll' 'System.Management.dll' 'System.Security.Claims.dll' - 'System.Security.Cryptography.Primitives.dll' 'System.Threading.Overlapped.dll' ) } else { $allowedAssemblies += @( - 'System.Collections.Immutable.dll' - 'System.IO.MemoryMappedFiles.dll' + 'System.Diagnostics.DiagnosticSource.dll' 'System.Net.Sockets.dll' - 'System.Reflection.Metadata.dll' ) } @@ -93,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..2280d141ac3 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,193 @@ 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() + } + } + + It "Issue#26277 - [CompletionCompleters]::CompleteFilename('') should work" { + $testDir = Join-Path $TestDrive "TempTestDir" + $file1 = Join-Path $testDir "abc.ps1" + $file2 = Join-Path $testDir "def.py" + + New-Item -ItemType Directory -Path $testDir > $null + New-Item -ItemType File -Path $file1 > $null + New-Item -ItemType File -Path $file2 > $null + + try { + Push-Location -Path $testDir + $result = [System.Management.Automation.CompletionCompleters]::CompleteFilename("") + $result | Should -Not -Be $null + $result | Measure-Object | ForEach-Object -MemberName Count | Should -Be 2 + + $item1, $item2 = @($result) + $item1.ListItemText | Should -BeExactly 'abc.ps1' + $item2.ListItemText | Should -BeExactly 'def.py' + } finally { + Pop-Location + } + } + + Context 'Native CLI argument completion' { + BeforeAll { + $testDir = Join-Path $TestDrive "TempTestDir" + $file1 = Join-Path $testDir "abc.ps1" + $file2 = Join-Path $testDir "def.py" + + New-Item -ItemType Directory -Path $testDir > $null + New-Item -ItemType File -Path $file1 > $null + New-Item -ItemType File -Path $file2 > $null + + $dirSep = [System.IO.Path]::DirectorySeparatorChar + $relative_name_abc = ".${dirSep}abc.ps1" + $relative_name_def = ".${dirSep}def.py" + } + + AfterAll { + ## Unregister the completer for 'ping' to avoid affecting other tests. + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock $null + } + + It 'Completer script block returning nothing should fall back to file name completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches | Should -Not -BeNullOrEmpty + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning $null should suppress default completion fallback' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return $null + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should suppress the default file name completion fallback, returning no results. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 0 + } finally { + Pop-Location + } + } + + It 'Completer script block returning empty string should suppress default completion fallback' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should suppress the default file name completion fallback, returning no results. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 0 + } finally { + Pop-Location + } + } + + It 'Completer script block returning empty-string-only array should fall back to default completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '', '' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should fall back to the default completion. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning null-value-only array should fall back to default completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return $null, $null + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should fall back to the default completion. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning a single string works as expected' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return 'hello' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 1 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "hello" + } finally { + Pop-Location + } + } + + It 'Completer script block returning an array that contains non-empty-or-null strings works as expected' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '', 'hello', $null, 'world' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "hello" + $result.CompletionMatches[1].CompletionText | Should -BeExactly "world" + } finally { + Pop-Location + } + } } } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index b8b1cc607a5..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -3,7 +3,6 @@ Describe "TabCompletion" -Tags CI { BeforeAll { $separator = [System.IO.Path]::DirectorySeparatorChar - $nullConditionalFeatureDisabled = -not $EnabledExperimentalFeatures.Contains('PSNullConditionalOperators') } It 'Should complete Command' { @@ -24,26 +23,108 @@ 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(' } - It 'Should complete dotnet method with null conditional operator' -Skip:$nullConditionalFeatureDisabled { + It 'Should complete dotnet method with null conditional operator' { $res = TabExpansion2 -inputScript '(1)?.ToSt' -cursorColumn '(1)?.ToSt'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'ToString(' } - It 'Should complete dotnet method with null conditional operator without first letter' -Skip:$nullConditionalFeatureDisabled { + It 'Should complete dotnet method with null conditional operator without first letter' { $res = TabExpansion2 -inputScript '(1)?.' -cursorColumn '(1)?.'.Length $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(' @@ -59,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' @@ -119,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' { @@ -147,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' @@ -164,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 } @@ -195,117 +795,1146 @@ Describe "TabCompletion" -Tags CI { $expected = '' } - $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - if ($res.CompletionMatches.Count -gt 0) { - $actual = [string]::Join(",",$res.CompletionMatches.completiontext) - } - else { - $actual = '' + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + if ($res.CompletionMatches.Count -gt 0) { + $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + } + else { + $actual = '' + } + + $actual | Should -BeExactly $expected + } + + It 'Should work for variable assignment of custom enum: ' -TestCases @( + @{ inputStr = '[Animal]$c="g'; expected = '"Giraffe"','"Goose"' } + @{ inputStr = '[Animal]$c='; expected = "'Duck'","'Giraffe'","'Goose'","'Horse'" } + @{ inputStr = '$script:test = "g'; expected = '"Giraffe"','"Goose"' } + @{ inputStr = '$script:test='; expected = "'Duck'","'Giraffe'","'Goose'","'Horse'" } + @{ inputStr = '$script:test = "x'; expected = @() } + ){ + param($inputStr, $expected) + + enum Animal { Duck; Goose; Horse; Giraffe } + [Animal]$script:test = 'Duck' + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + if ($res.CompletionMatches.Count -gt 0) { + $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + } + else { + $actual = '' + } + + $actual | Should -BeExactly ([string]::Join(",",$expected)) + } + + It 'Should work for assignment of variable with validateset of strings: ' -TestCases @( + @{ inputStr = '$test='; expected = "'a'","'aa'","'aab'","'b'"; doubleQuotes = $false } + @{ inputStr = '$test="a'; expected = "'a'","'aa'","'aab'"; doubleQuotes = $true } + @{ inputStr = '$test = "aa'; expected = "'aa'","'aab'"; doubleQuotes = $true } + @{ inputStr = '$test=''aab'; expected = "'aab'"; doubleQuotes = $false } + @{ inputStr = '$test="c'; expected = ''; doubleQuotes = $true } + ){ + param($inputStr, $expected, $doubleQuotes) + + [ValidateSet('a','aa','aab','b')][string]$test = 'b' + + $expected = [string]::Join(",",$expected) + if ($doubleQuotes) { + $expected = $expected.Replace("'", """") + } + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + if ($res.CompletionMatches.Count -gt 0) { + $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + } + else { + $actual = '' + } + + $actual | Should -BeExactly $expected + } + + It 'Should work for assignment of variable with validateset of int: ' -TestCases @( + @{ inputStr = '$test='; expected = 2,3,11,112 } + @{ inputStr = '$test = 1'; expected = 11,112 } + @{ inputStr = '$test =11'; expected = 11,112 } + @{ inputStr = '$test =4'; expected = @() } + ){ + param($inputStr, $expected) + + [ValidateSet(2,3,11,112)][int]$test = 2 + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + if ($res.CompletionMatches.Count -gt 0) { + $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + } + else { + $actual = '' + } + + $actual | Should -BeExactly ([string]::Join(",",$expected)) + } + + It 'Should work for assignment of variable with validateset of strings: ' -TestCases @( + @{ inputStr = '[validateset("a","aa","aab","b")][string]$test='; expected = "'a'","'aa'","'aab'","'b'"; doubleQuotes = $false } + @{ inputStr = '[validateset("a","aa","aab","b")][string]$test="a'; expected = "'a'","'aa'","'aab'"; doubleQuotes = $true } + @{ inputStr = '[validateset("a","aa","aab","b")][string]$test = "aa'; expected = "'aa'","'aab'"; doubleQuotes = $true } + @{ inputStr = '[validateset("a","aa","aab","b")][string]$test=''aab'; expected = "'aab'"; doubleQuotes = $false } + @{ inputStr = '[validateset("a","aa","aab","b")][string]$test=''c'; expected = ''; doubleQuotes = $false } + ){ + param($inputStr, $expected, $doubleQuotes) + + $expected = [string]::Join(",",$expected) + if ($doubleQuotes) { + $expected = $expected.Replace("'", """") + } + + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + if ($res.CompletionMatches.Count -gt 0) { + $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + } + else { + $actual = '' + } + + $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' + } + + It 'Should include parameter help message in tool tip - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + Function Test-Function { + param ( + [Parameter(HelpMessage = 'Some help message')] + $ParamWithHelp, + + $ParamWithoutHelp + ) + } + + $expected = '[Object] ParamWithHelp - Some help message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-ParamWithHelp' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should include parameter help resource message in tool tip - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + $expected = '`[string`] Activity - *' + + if ($SingleMatch) { + $Script = 'Write-Progress -Activity' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Write-Progress -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Activity' + $res.ToolTip | Should -BeLikeExactly $expected + } + + It 'Should skip empty parameter HelpMessage with multiple parameters - SingleMatch ' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + Function Test-Function { + [CmdletBinding(DefaultParameterSetName = 'SetWithoutHelp')] + param ( + [Parameter(ParameterSetName = 'SetWithHelp', HelpMessage = 'Help Message')] + [Parameter(ParameterSetName = 'SetWithoutHelp')] + [string] + $ParamWithHelp, + + [Parameter(ParameterSetName = 'SetWithHelp')] + [switch] + $ParamWithoutHelp + ) + } + + $expected = '[string] ParamWithHelp - Help Message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-ParamWithHelp' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should retrieve help message from dynamic parameter' { + Function Test-Function { + [CmdletBinding()] + param () + dynamicparam { + $attr = [System.Management.Automation.ParameterAttribute]@{ + HelpMessage = "Howdy partner" + } + $attrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() + $attrCollection.Add($attr) + + $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('DynamicParam', [string], $attrCollection) + + $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDictionary.Add('DynamicParam', $dynParam) + $paramDictionary + } + + end {} + } + + $expected = '[string] DynamicParam - Howdy partner' + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-DynamicParam' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-DynamicParam' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should have type and name for parameter without help message' { + Function Test-Function { + param ( + [Parameter()] + $WithParamAttribute, + + $WithoutParamAttribute + ) + } + + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | + Where-Object CompletionText -in '-WithParamAttribute', '-WithoutParamAttribute' | + Sort-Object CompletionText + $res.Count | Should -Be 2 + + $res.CompletionText[0] | Should -BeExactly '-WithoutParamAttribute' + $res.ToolTip[0] | Should -BeExactly '[Object] WithoutParamAttribute' + + $res.CompletionText[1] | Should -BeExactly '-WithParamAttribute' + $res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute' + } + + It 'Should ignore errors when faling to get HelpMessage resource' { + Function Test-Function { + param ( + [Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")] + $InvalidHelpParam + ) + } + + $expected = '[Object] InvalidHelpParam' + $Script = 'Test-Function -InvalidHelpParam' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-InvalidHelpParam' + $res.ToolTip | Should -BeExactly $expected + } + + Context 'Start-Process -Verb parameter completion' { + BeforeAll { + function GetProcessInfoVerbs([string]$path, [switch]$singleQuote, [switch]$doubleQuote) { + $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 + } } - $actual | Should -BeExactly $expected + AfterAll { + if ($IsWindows) { + Remove-Item -Path $registryPath -Force + Remove-Item -LiteralPath $registryLiteralPath -Force + Remove-Item -Path $fileSystemPath -Force + Remove-Item -LiteralPath $fileSystemLiteralPathDir -Recurse -Force + } + } } - It 'Should work for variable assignment of custom enum: ' -TestCases @( - @{ inputStr = '[Animal]$c="g'; expected = '"Giraffe"','"Goose"' } - @{ inputStr = '[Animal]$c='; expected = "'Duck'","'Giraffe'","'Goose'","'Horse'" } - @{ inputStr = '$script:test = "g'; expected = '"Giraffe"','"Goose"' } - @{ inputStr = '$script:test='; expected = "'Duck'","'Giraffe'","'Goose'","'Horse'" } - @{ inputStr = '$script:test = "x'; expected = @() } - ){ - param($inputStr, $expected) + Context 'Get-Command -Noun parameter completion' { + BeforeAll { + function GetModuleCommandNouns( + [string]$Module, + [string]$Verb, + [switch]$SingleQuote, + [switch]$DoubleQuote) + { - enum Animal { Duck; Goose; Horse; Giraffe } - [Animal]$script:test = 'Duck' + $commandParams = @{} - $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - if ($res.CompletionMatches.Count -gt 0) { - $actual = [string]::Join(",",$res.CompletionMatches.completiontext) - } - else { - $actual = '' - } + if ($PSBoundParameters.ContainsKey('Module')) { + $commandParams['Module'] = $Module + } - $actual | Should -BeExactly ([string]::Join(",",$expected)) - } + if ($PSBoundParameters.ContainsKey('Verb')) { + $commandParams['Verb'] = $Verb + } - It 'Should work for assignment of variable with validateset of strings: ' -TestCases @( - @{ inputStr = '$test='; expected = "'a'","'aa'","'aab'","'b'"; doubleQuotes = $false } - @{ inputStr = '$test="a'; expected = "'a'","'aa'","'aab'"; doubleQuotes = $true } - @{ inputStr = '$test = "aa'; expected = "'aa'","'aab'"; doubleQuotes = $true } - @{ inputStr = '$test=''aab'; expected = "'aab'"; doubleQuotes = $false } - @{ inputStr = '$test="c'; expected = ''; doubleQuotes = $true } - ){ - param($inputStr, $expected, $doubleQuotes) + $nouns = (Get-Command @commandParams).Noun - [ValidateSet('a','aa','aab','b')][string]$test = 'b' + if ($SingleQuote) { + return ($nouns | ForEach-Object { "'$_'" }) + } + elseif ($DoubleQuote) { + return ($nouns | ForEach-Object { """$_""" }) + } - $expected = [string]::Join(",",$expected) - if ($doubleQuotes) { - $expected = $expected.Replace("'", """") + 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""" } } - $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - if ($res.CompletionMatches.Count -gt 0) { - $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + 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 ' ') } - else { - $actual = '' + } + + 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*" } } - $actual | Should -BeExactly $expected + 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 ' ') + } } - It 'Should work for assignment of variable with validateset of int: ' -TestCases @( - @{ inputStr = '$test='; expected = 2,3,11,112 } - @{ inputStr = '$test = 1'; expected = 11,112 } - @{ inputStr = '$test =11'; expected = 11,112 } - @{ inputStr = '$test =4'; expected = @() } - ){ - param($inputStr, $expected) + 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}""" + } - [ValidateSet(2,3,11,112)][int]$test = 2 + $commaSeparators = "',' ', '" + $semiColonSeparators = "';' '; '" + + $squareBracketFormatString = "'[{0}]'" + $curlyBraceFormatString = "'{0:N2}'" + } - $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - if ($res.CompletionMatches.Count -gt 0) { - $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + 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 + } } - else { - $actual = '' + + 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 + } } - $actual | Should -BeExactly ([string]::Join(",",$expected)) + 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 + } + } } - It 'Should work for assignment of variable with validateset of strings: ' -TestCases @( - @{ inputStr = '[validateset("a","aa","aab","b")][string]$test='; expected = "'a'","'aa'","'aab'","'b'"; doubleQuotes = $false } - @{ inputStr = '[validateset("a","aa","aab","b")][string]$test="a'; expected = "'a'","'aa'","'aab'"; doubleQuotes = $true } - @{ inputStr = '[validateset("a","aa","aab","b")][string]$test = "aa'; expected = "'aa'","'aab'"; doubleQuotes = $true } - @{ inputStr = '[validateset("a","aa","aab","b")][string]$test=''aab'; expected = "'aab'"; doubleQuotes = $false } - @{ inputStr = '[validateset("a","aa","aab","b")][string]$test=''c'; expected = ''; doubleQuotes = $false } - ){ - param($inputStr, $expected, $doubleQuotes) + 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 + + + + + + + + +'@ - $expected = [string]::Join(",",$expected) - if ($doubleQuotes) { - $expected = $expected.Replace("'", """") - } + $tempViewFile = Join-Path -Path $TestDrive -ChildPath 'processViewDefinition.ps1xml' + Set-Content -LiteralPath $tempViewFile -Value $viewDefinition -Force - $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length - if ($res.CompletionMatches.Count -gt 0) { - $actual = [string]::Join(",",$res.CompletionMatches.completiontext) + $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 } - else { - $actual = '' + + 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 } - $actual | 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 + ## Find a native command that is not 'pwsh'. We will use 'pwsh' for fallback completer tests later. + $nativeCommand = Get-Command -CommandType Application -TotalCount 2 | + Where-Object Name -NotLike pwsh* | + Select-Object -First 1 } + It 'Completes native commands with -' { Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { param($wordToComplete, $ast, $cursorColumn) @@ -369,6 +1998,52 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches.CompletionText | Should -BeExactly "-option" } + + It 'Covers an arbitrary unbound native command with -t' { + ## Register a completer for $nativeCommand. + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-t') { + return "-terminal" + } + } + + ## Register a fallback native command completer. + Register-ArgumentCompleter -NativeFallback -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-t') { + return "-testing" + } + } + + ## The specific completer will be used if it exists. + $line = "$nativeCommand -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-terminal" + + ## Otherwise, the fallback completer will kick in. + $line = "pwsh -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-testing" + + ## Remove the completer for $nativeCommand. + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock $null + + ## The fallback completer will be used for $nativeCommand. + $line = "$nativeCommand -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-testing" + + ## Remove the fallback completer for $nativeCommand. + Register-ArgumentCompleter -NativeFallback -ScriptBlock $null + + ## The fallback completer will be used for $nativeCommand. + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 0 + } } It 'Should complete "Export-Counter -FileFormat" with available output formats' -Pending { @@ -378,6 +2053,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 "" @@ -423,6 +2184,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" @@ -573,12 +2364,166 @@ Describe "TabCompletion" -Tags CI { $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 + + $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 "Should add single quotes if there are double quotes in bare word file path" { + $BadQuote = [char]8220 + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${BadQuote}File" + $null = New-Item -Path $TestFile1 -Force + $res = TabExpansion2 -inputScript "Get-ChildItem -Path $TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be "'$TestFile1'" + Remove-Item -LiteralPath $TestFile1 -Force + } + + It "Should escape double quote if the input string uses double quotes" { + $BadQuote = [char]8220 + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${BadQuote}File" + $null = New-Item -Path $TestFile1 -Force + $res = TabExpansion2 -inputScript "Get-ChildItem -Path `"$TestDrive\" + $Expected = "`"$($TestFile1.Insert($TestFile1.LastIndexOf($BadQuote), '`'))`"" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + Remove-Item -LiteralPath $TestFile1 -Force + } + + It "Should escape single quotes in file paths" { + $SingleQuote = "'" + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${SingleQuote}File" + $null = New-Item -Path $TestFile1 -Force + # Regardless if the input string was singlequoted or not, we expect to add surrounding single quotes and + # escape the single quote in the file path with another singlequote. + $Expected = "'$($TestFile1.Insert($TestFile1.LastIndexOf($SingleQuote), "'"))'" + + $res = TabExpansion2 -inputScript "Get-ChildItem -Path '$TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + + $res = TabExpansion2 -inputScript "Get-ChildItem -Path $TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + + Remove-Item -LiteralPath $TestFile1 -Force + } + } + + It 'Should correct slashes in UNC path completion' -Skip:(!$IsWindows) { + $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" } @@ -628,7 +2573,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} } } @@ -678,6 +2623,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 } @@ -699,7 +2645,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 @@ -714,7 +2660,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 } ) } @@ -728,17 +2674,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 @@ -746,7 +2705,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 @@ -759,7 +2718,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 @@ -837,6 +2796,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 { @@ -870,11 +2844,51 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches | Should -HaveCount 16 $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Black' - $inputStr = "baz Black " + $inputStr = "baz Black " + $res = TabExpansion2 -inputScript $inputStr -cursorColumn $inputStr.Length + $res.CompletionMatches | Should -HaveCount 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'cat' + $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 'cat' - $res.CompletionMatches[1].CompletionText | Should -BeExactly 'dog' + $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" { @@ -885,6 +2899,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 @@ -909,6 +2953,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) @@ -917,6 +3030,62 @@ Describe "TabCompletion" -Tags CI { $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 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 ` @@ -944,8 +3113,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 } } @@ -964,7 +3224,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" @@ -1016,6 +3276,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 @@ -1106,31 +3381,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 = @( @@ -1146,6 +3396,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" { @@ -1170,6 +3427,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" { @@ -1207,6 +3472,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 @@ -1241,6 +3511,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 " } ) } @@ -1251,26 +3533,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) @@ -1279,29 +3572,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 + } + } - # 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 complete module specification keys in using module statement' { + $res = TabExpansion2 -inputScript 'using module @{' + $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly "GUID MaximumVersion ModuleName ModuleVersion RequiredVersion" + $res.CompletionMatches[0].ToolTip | Should -Not -Be $res.CompletionMatches[0].CompletionText + } + + It 'Should not fallback to file completion when completing typenames' { + $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 @@ -1319,11 +3914,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 { @@ -1391,7 +3991,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"} ) { @@ -1412,4 +4012,185 @@ Describe "WSMan Config Provider tab complete tests" -Tags Feature,RequireAdminOn # https://github.com/PowerShell/PowerShell/issues/4744 # TODO: move to test cases above once working } + + Context "Tab completion for switch cases on `$PSBoundParameters.Keys" { + It "Should complete parameter names in switch case for `$PSBoundParameters.Keys" { + $inputScript = @" +function Test-Func { + param( + [string]`$Param1, + [string]`$Param2, + [int]`$Count + ) + switch (`$PSBoundParameters.Keys) { + P + } +} +"@ + $cursorPosition = $inputScript.IndexOf("P", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "Param1" + $completionTexts[1] | Should -BeExactly "Param2" + } + + It "Should complete all parameter names when prefix matches single param" { + $inputScript = @" +function Test-Func { + param( + [string]`$Name, + [int]`$Value + ) + switch (`$PSBoundParameters.Keys) { + N + } +} +"@ + $cursorPosition = $inputScript.IndexOf("N", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Name" + } + + It "Should complete parameter names in scriptblock param" { + $inputScript = @" +`$sb = { + param( + [string]`$ScriptParam1, + [string]`$ScriptParam2 + ) + switch (`$PSBoundParameters.Keys) { + S + } +} +"@ + $cursorPosition = $inputScript.IndexOf("S", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "ScriptParam1" + $completionTexts[1] | Should -BeExactly "ScriptParam2" + } + } + + Context "Tab completion for `$PSBoundParameters access patterns" { + It "Should complete parameter names for ContainsKey method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + if (`$PSBoundParameters.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for indexer access" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$value = `$PSBoundParameters['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for Remove method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$PSBoundParameters.Remove('C') +} +"@ + $cursorPosition = $inputScript.IndexOf("'C'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Count'" + } + + It "Should complete with double quotes when using double-quoted string" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2) + if (`$PSBoundParameters.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly '"Param1"' + $completionTexts[1] | Should -BeExactly '"Param2"' + } + + It "Should not complete for non-PSBoundParameters variable with indexer" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$value = `$hash['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with ContainsKey" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with Remove" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$hash.Remove('P') +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with double quotes" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq '"Param1"' } + $paramCompletion | Should -BeNullOrEmpty + } + } } diff --git a/test/powershell/Installer/WindowsInstaller.Tests.ps1 b/test/powershell/Installer/WindowsInstaller.Tests.ps1 deleted file mode 100644 index d1ce05e8f23..00000000000 --- a/test/powershell/Installer/WindowsInstaller.Tests.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Describe "Windows Installer" -Tags "Scenario" { - - BeforeAll { - $skipTest = -not $IsWindows - $preRequisitesLink = 'https://aka.ms/pscore6-prereq' - $linkCheckTestCases = @( - @{ Name = "Universal C Runtime"; Url = $preRequisitesLink } - @{ Name = "WMF 4.0"; Url = "https://www.microsoft.com/download/details.aspx?id=40855" } - @{ Name = "WMF 5.0"; Url = "https://www.microsoft.com/download/details.aspx?id=50395" } - @{ Name = "WMF 5.1"; Url = "https://www.microsoft.com/download/details.aspx?id=54616" } - ) - } - - It "WiX (Windows Installer XML) file contains pre-requisites link $preRequisitesLink" -Skip:$skipTest { - $wixProductFile = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\assets\Product.wxs" - (Get-Content $wixProductFile -Raw).Contains($preRequisitesLink) | Should -BeTrue - } - - ## Running 'Invoke-WebRequest' with WMF download URLs has been failing intermittently, - ## because sometimes the URLs lead to a 'this download is no longer available' page. - ## We use a retry logic here. Retry for 5 times with 1 second interval. - It "Pre-Requisistes link for '' is reachable: " -TestCases $linkCheckTestCases -Skip:$skipTest { - param ($Url) - - foreach ($i in 1..5) { - try { - $result = Invoke-WebRequest $Url -UseBasicParsing - break; - } catch { - Start-Sleep -Seconds 1 - } - } - - $result | Should -Not -Be $null - } -} diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index 9bef836e19b..6674697ca2f 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 @@ -820,7 +833,7 @@ class Derived : Base [Derived]::new().foo() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) @@ -916,7 +929,7 @@ class A : Foo.Bar return [A]::new() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Language/Classes/Scripting.Classes.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/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index fb3778463da..d4e86e341b1 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -417,7 +417,7 @@ function foo() '@ # resolve name to absolute path $scriptToProcessPath = (Get-ChildItem $scriptToProcessPath).FullName - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.StartupScripts.Add($scriptToProcessPath) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Language/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/NullConditional.Tests.ps1 b/test/powershell/Language/Operators/NullConditional.Tests.ps1 index 5c2c65e4ad9..237b4d8c987 100644 --- a/test/powershell/Language/Operators/NullConditional.Tests.ps1 +++ b/test/powershell/Language/Operators/NullConditional.Tests.ps1 @@ -260,31 +260,6 @@ Describe 'NullCoalesceOperations' -Tags 'CI' { Describe 'NullConditionalMemberAccess' -Tag 'CI' { - BeforeAll { - $skipTest = -not $EnabledExperimentalFeatures.Contains('PSNullConditionalOperators') - - if ($skipTest) { - Write-Verbose "Test Suite Skipped. The test suite requires the experimental feature 'PSNullConditionalOperators' to be enabled." -Verbose - $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() - $PSDefaultParameterValues["it:skip"] = $true - } - - function ExecuteTestIfFeatureIsEnabled([string] $TestContet) - { - if ($skipTest) { - Set-ItResult -Skipped -Because "PSNullConditionalOperators feature is disabled" - } else { - Invoke-Expression $testContent - } - } - } - - AfterAll { - if ($skipTest) { - $global:PSDefaultParameterValues = $originalDefaultParameterValues - } - } - Context '?. operator tests' { BeforeAll { $psObj = [psobject]::new() @@ -302,8 +277,6 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { } It 'Can get member value of a non-null variable' { - - $testContent = @' ${psObj}?.name | Should -BeExactly 'value' ${array}?.length | Should -Be 3 ${hash}?.a | Should -Be 1 @@ -311,81 +284,50 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { (Get-Item $TestDrive)?.EnumerateFiles()?.Name | Should -BeExactly 'testfile.txt' [int32]::MaxValue?.ToString() | Should -BeExactly '2147483647' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Can get null when variable is null' { - $testContent = @' ${nonExistent}?.name | Should -BeNullOrEmpty ${nonExistent}?.MyMethod() | Should -BeNullOrEmpty (get-process -Name doesnotexist -ErrorAction SilentlyContinue)?.Id | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. operator multiple times in statement' { - $testContent = @' ${psObj}?.name?.nonExistent | Should -BeNullOrEmpty ${psObj}?.nonExistent?.nonExistent | Should -BeNullOrEmpty ${nonExistent}?.nonExistent?.nonExistent | Should -BeNullOrEmpty ${psObj}?.nested?.name | Should -BeExactly 'valuenested' ${psObj}?.nestedMethod?.GetHello() | Should -BeExactly 'hello' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name' { - $testContent = @' $methodName = 'ToLongDateString' (Get-Date '11/11/2019')?.$methodName() | Should -BeExactly 'Monday, November 11, 2019' ${doesNotExist}?.$methodName() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name that does not exist' { - $testContent = @' $methodName = 'DoesNotExist' { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic method name that does not exist' { - $testContent = @' $methodName = $null { (Get-Date '11/11/2019')?.$methodName() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Use ?. on a dynamic property name' { - $testContent = @' $propName = 'SI' (Get-Process -Id $PID)?.$propName | Should -Be (Get-Process -id $PID).SessionId ${doesNotExist}?.$propName() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Should throw error when method does not exist' { - $testContent = @' { ${psObj}?.nestedMethod?.NonExistent() } | Should -Throw -ErrorId 'MethodNotFound' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } } @@ -401,46 +343,30 @@ Describe 'NullConditionalMemberAccess' -Tag 'CI' { } It 'Can index can call properties' { - $testContent = @' ${array}?[0] | Should -Be 1 ${array}?[0,1] | Should -Be @(1,2) ${array}?[0..2] | Should -Be @(1,2,3) ${array}?[-2] | Should -Be 2 ${hash}?['a'] | Should -Be 1 -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Indexing in null items should be null' { - $testContent = @' ${doesnotExist}?[0] | Should -BeNullOrEmpty ${doesnotExist}?[0,1] | Should -BeNullOrEmpty ${doesnotExist}?[0..2] | Should -BeNullOrEmpty ${doesnotExist}?[-2] | Should -BeNullOrEmpty ${doesnotExist}?['a'] | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Can call methods on indexed items' { - $testContent = @' ${dateArray}?[0]?.ToLongDateString() | Should -BeExactly 'Friday, November 1, 2019' -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } It 'Calling a method on nonexistent item give null' { - $testContent = @' ${dateArray}?[1234]?.ToLongDateString() | Should -BeNullOrEmpty ${doesNotExist}?[0]?.MyGetMethod() | Should -BeNullOrEmpty -'@ - - ExecuteTestIfFeatureIsEnabled -TestContent $testContent } } } 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 59d7039898a..074351d3414 100644 --- a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 @@ -11,6 +11,12 @@ Describe "Replace Operator" -Tags CI { $res | Should -BeExactly "image.jpg" } + It "Replace operator can convert an substitution object to string" { + $substitution = Get-Process -Id $pid + $res = "!3!" -replace "3",$substitution + $res | Should -BeExactly "!System.Diagnostics.Process (pwsh)!" + } + It "Replace operator can be case-insensitive and case-sensitive" { $res = "book" -replace "B","C" $res | Should -BeExactly "Cook" @@ -29,6 +35,18 @@ Describe "Replace Operator" -Tags CI { $res = "PowerPoint" -replace "Point" $res | Should -BeExactly "Power" } + + It "Replace operator can take an enumerable as first argument, a mandatory pattern, and an optional substitution" { + $res = "PowerPoint1","PowerPoint2" -replace "Point","Shell" + $res.Count | Should -Be 2 + $res[0] | Should -BeExactly "PowerShell1" + $res[1] | Should -BeExactly "PowerShell2" + + $res = "PowerPoint1","PowerPoint2" -replace "Point" + $res.Count | Should -Be 2 + $res[0] | Should -BeExactly "Power1" + $res[1] | Should -BeExactly "Power2" + } } Context "Replace operator substitutions" { @@ -67,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 f9a981060cd..f6874b1db93 100644 --- a/test/powershell/Language/Parser/Conversions.Tests.ps1 +++ b/test/powershell/Language/Parser/Conversions.Tests.ps1 @@ -34,27 +34,30 @@ Describe 'conversion syntax' -Tags "CI" { ## would become `, [List[int]]@(1,2)`, which is more confusing than `$result = [List[int]]@(1,2)`. ## This is why the current form of `$result = [List[int]]@(1,2)` is used intentionally here. - @{ Command = {$result = [Collections.Generic.List[int]]@(1)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1) } - @{ Command = {$result = [Collections.Generic.List[int]]@(1,2)}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1,2) } - @{ Command = {$result = [Collections.Generic.List[int]]"4"}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4) } - @{ Command = {$result = [Collections.Generic.List[int]]@("4","5")}; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4,5) } - - @{ Command = {$result = [Collections.Generic.List[string]]@(1)}; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1") } - @{ Command = {$result = [Collections.Generic.List[string]]@(1,2)}; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1","2") } - @{ Command = {$result = [Collections.Generic.List[string]]1}; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1") } - @{ Command = {$result = [Collections.Generic.List[string]]@("4")}; CollectionType = 'List`1'; ElementType = "String"; Elements = @("4") } - - @{ Command = {$result = [System.Collections.ObjectModel.Collection[int]]@(1)}; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(1) } - @{ Command = {$result = [System.Collections.ObjectModel.Collection[int]]@(1,2)}; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(1,2) } - @{ Command = {$result = [System.Collections.ObjectModel.Collection[int]]"4"}; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(4) } - @{ Command = {$result = [System.Collections.ObjectModel.Collection[int]]@("4","5")}; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(4,5) } - - @{ Command = {$result = [Collections.Generic.List[System.IO.FileInfo]]@('TestFile')}; - CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') } - @{ Command = {$result = [Collections.Generic.List[System.IO.FileInfo]]@('TestFile1', 'TestFile2')}; - CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile1', 'TestFile2') } - @{ Command = {$result = [Collections.Generic.List[System.IO.FileInfo]]'TestFile'}; - CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') } + @{ Command = { $result = [Collections.Generic.List[int]]@(1) }; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1) } + @{ Command = { $result = [Collections.Generic.List[int]]@(1, 2) }; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(1, 2) } + @{ Command = { $result = [Collections.Generic.List[int]]"4" }; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4) } + @{ Command = { $result = [Collections.Generic.List[int]]@("4", "5") }; CollectionType = 'List`1'; ElementType = "Int32"; Elements = @(4, 5) } + + @{ Command = { $result = [Collections.Generic.List[string]]@(1) }; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1") } + @{ Command = { $result = [Collections.Generic.List[string]]@(1, 2) }; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1", "2") } + @{ Command = { $result = [Collections.Generic.List[string]]1 }; CollectionType = 'List`1'; ElementType = "String"; Elements = @("1") } + @{ Command = { $result = [Collections.Generic.List[string]]@("4") }; CollectionType = 'List`1'; ElementType = "String"; Elements = @("4") } + + @{ Command = { $result = [System.Collections.ObjectModel.Collection[int]]@(1) }; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(1) } + @{ Command = { $result = [System.Collections.ObjectModel.Collection[int]]@(1, 2) }; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(1, 2) } + @{ Command = { $result = [System.Collections.ObjectModel.Collection[int]]"4" }; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(4) } + @{ Command = { $result = [System.Collections.ObjectModel.Collection[int]]@("4", "5") }; CollectionType = 'Collection`1'; ElementType = "Int32"; Elements = @(4, 5) } + + @{ Command = { $result = [Collections.Generic.List[System.IO.FileInfo]]@('TestFile') }; + CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') + } + @{ Command = { $result = [Collections.Generic.List[System.IO.FileInfo]]@('TestFile1', 'TestFile2') }; + CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile1', 'TestFile2') + } + @{ Command = { $result = [Collections.Generic.List[System.IO.FileInfo]]'TestFile' }; + CollectionType = 'List`1'; ElementType = "FileInfo"; Elements = @('TestFile') + } ) } @@ -75,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" { @@ -106,12 +117,12 @@ namespace TestTypeResolution { } '@ - $cmdletDllDir = Join-Path $TestDrive "cmdlet" + $cmdletDllDir = Join-Path $TestDrive "cmdlet" $dupTypeDllDir = Join-Path $TestDrive "dupType" $null = New-Item -Path $cmdletDllDir, $dupTypeDllDir -ItemType Directory -Force - $cmdletDllPath = Join-Path $cmdletDllDir "TestCmdlet.dll" + $cmdletDllPath = Join-Path $cmdletDllDir "TestCmdlet.dll" $dupTypeDllPath = Join-Path $dupTypeDllDir "TestType.dll" Add-Type $cmdletCode -OutputAssembly $cmdletDllPath @@ -147,82 +158,82 @@ Describe 'method conversion' -Tags 'CI' { } # check that we can handle at least 72 overloads - static [char] Foo([char] $i) {return $i} - static [char] Foo([char] $i, [char] $j) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n, [char] $o) {return $i} - static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n, [char] $o, [char] $p) {return $i} - static [int16] Foo([int16] $i) {return $i} - static [int16] Foo([int16] $i, [int16] $j) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n, [int16] $o) {return $i} - static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n, [int16] $o, [int16] $p) {return $i} - static [int] Foo([int] $i) {return $i} - static [int] Foo([int] $i, [int] $j) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n, [int] $o) {return $i} - static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n, [int] $o, [int] $p) {return $i} - static [UInt32] Foo([UInt32] $i) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n, [UInt32] $o) {return $i} - static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n, [UInt32] $o, [UInt32] $p) {return $i} - static [UInt64] Foo([UInt64] $i) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n, [UInt64] $o) {return $i} - static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n, [UInt64] $o, [UInt64] $p) {return $i} - static [float] Foo([float] $i) {return $i} - static [float] Foo([float] $i, [float] $j) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n, [float] $o) {return $i} - static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n, [float] $o, [float] $p) {return $i} - static [double] Foo([double] $i) {return $i} - static [double] Foo([double] $i, [double] $j) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n, [double] $o) {return $i} - static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n, [double] $o, [double] $p) {return $i} - static [IntPtr] Foo([IntPtr] $i) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n, [IntPtr] $o) {return $i} - static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n, [IntPtr] $o, [IntPtr] $p) {return $i} - static [timespan] Foo([timespan] $i) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n, [timespan] $o) {return $i} - static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n, [timespan] $o, [timespan] $p) {return $i} + static [char] Foo([char] $i) { return $i } + static [char] Foo([char] $i, [char] $j) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n, [char] $o) { return $i } + static [char] Foo([char] $i, [char] $j, [char] $k, [char] $l, [char] $m, [char] $n, [char] $o, [char] $p) { return $i } + static [int16] Foo([int16] $i) { return $i } + static [int16] Foo([int16] $i, [int16] $j) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n, [int16] $o) { return $i } + static [int16] Foo([int16] $i, [int16] $j, [int16] $k, [int16] $l, [int16] $m, [int16] $n, [int16] $o, [int16] $p) { return $i } + static [int] Foo([int] $i) { return $i } + static [int] Foo([int] $i, [int] $j) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n, [int] $o) { return $i } + static [int] Foo([int] $i, [int] $j, [int] $k, [int] $l, [int] $m, [int] $n, [int] $o, [int] $p) { return $i } + static [UInt32] Foo([UInt32] $i) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n, [UInt32] $o) { return $i } + static [UInt32] Foo([UInt32] $i, [UInt32] $j, [UInt32] $k, [UInt32] $l, [UInt32] $m, [UInt32] $n, [UInt32] $o, [UInt32] $p) { return $i } + static [UInt64] Foo([UInt64] $i) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n, [UInt64] $o) { return $i } + static [UInt64] Foo([UInt64] $i, [UInt64] $j, [UInt64] $k, [UInt64] $l, [UInt64] $m, [UInt64] $n, [UInt64] $o, [UInt64] $p) { return $i } + static [float] Foo([float] $i) { return $i } + static [float] Foo([float] $i, [float] $j) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n, [float] $o) { return $i } + static [float] Foo([float] $i, [float] $j, [float] $k, [float] $l, [float] $m, [float] $n, [float] $o, [float] $p) { return $i } + static [double] Foo([double] $i) { return $i } + static [double] Foo([double] $i, [double] $j) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n, [double] $o) { return $i } + static [double] Foo([double] $i, [double] $j, [double] $k, [double] $l, [double] $m, [double] $n, [double] $o, [double] $p) { return $i } + static [IntPtr] Foo([IntPtr] $i) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n, [IntPtr] $o) { return $i } + static [IntPtr] Foo([IntPtr] $i, [IntPtr] $j, [IntPtr] $k, [IntPtr] $l, [IntPtr] $m, [IntPtr] $n, [IntPtr] $o, [IntPtr] $p) { return $i } + static [timespan] Foo([timespan] $i) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n, [timespan] $o) { return $i } + static [timespan] Foo([timespan] $i, [timespan] $j, [timespan] $k, [timespan] $l, [timespan] $m, [timespan] $n, [timespan] $o, [timespan] $p) { return $i } } It 'converts static method as Func does not throw' { - {[Func[int, int]] [M]::Thrice} | Should -Not -Throw + { [Func[int, int]] [M]::Thrice } | Should -Not -Throw } It 'converts static method as Func is non null' { @@ -245,7 +256,7 @@ Describe 'method conversion' -Tags 'CI' { It 'converts instance psmethodinfo to Func' { $m = [M]::new() - {[Func[int, int]] $m.Twice} | Should -Not -Throw + { [Func[int, int]] $m.Twice } | Should -Not -Throw $f = [Func[int, int16, int]] [M]::Add $f.Invoke(2, 6) | Should -Be 8 @@ -409,32 +420,32 @@ Describe 'method conversion' -Tags 'CI' { ## Different methods with same overload signatures. ## The second and third overloads match the target delegate with variance. - [string] GetA([int] $i, [string] $s) { return "GetA-int-string-string" } + [string] GetA([int] $i, [string] $s) { return "GetA-int-string-string" } [string] GetA([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetA-filesysteminfo-object-string" } - [string] GetA([System.IO.FileInfo] $finfo, [object] $o) { return "GetA-fileinfo-object-string" } + [string] GetA([System.IO.FileInfo] $finfo, [object] $o) { return "GetA-fileinfo-object-string" } - [string] GetAPrime([int] $i, [string] $s) { return "GetAPrime-int-string-string" } + [string] GetAPrime([int] $i, [string] $s) { return "GetAPrime-int-string-string" } [string] GetAPrime([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetAPrime-filesysteminfo-object-string" } - [string] GetAPrime([System.IO.FileInfo] $finfo, [object] $o) { return "GetAPrime-fileinfo-object-string" } + [string] GetAPrime([System.IO.FileInfo] $finfo, [object] $o) { return "GetAPrime-fileinfo-object-string" } - static [string] GetAStatic([int] $i, [string] $s) { return "GetAStatic-int-string-string" } + static [string] GetAStatic([int] $i, [string] $s) { return "GetAStatic-int-string-string" } static [string] GetAStatic([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetAStatic-filesysteminfo-object-string" } - static [string] GetAStatic([System.IO.FileInfo] $finfo, [object] $o) { return "GetAStatic-fileinfo-object-string" } + static [string] GetAStatic([System.IO.FileInfo] $finfo, [object] $o) { return "GetAStatic-fileinfo-object-string" } ## Different methods with same overload signatures. ## The first overload matches the target delegate with variance, ## while the second overload matches the target delegate exactly. [string] GetB([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetB-filesysteminfo-object-string" } - [object] GetB([System.IO.FileInfo] $finfo, [string] $s) { return "GetB-fileinfo-string-object" } - [string] GetB([datetime] $d) { return "GetB-datetime-string" } + [object] GetB([System.IO.FileInfo] $finfo, [string] $s) { return "GetB-fileinfo-string-object" } + [string] GetB([datetime] $d) { return "GetB-datetime-string" } [string] GetBPrime([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetBPrime-filesysteminfo-object-string" } - [object] GetBPrime([System.IO.FileInfo] $finfo, [string] $s) { return "GetBPrime-fileinfo-string-object" } - [string] GetBPrime([datetime] $d) { return "GetBPrime-datetime-string" } + [object] GetBPrime([System.IO.FileInfo] $finfo, [string] $s) { return "GetBPrime-fileinfo-string-object" } + [string] GetBPrime([datetime] $d) { return "GetBPrime-datetime-string" } static [string] GetBStatic([System.IO.FileSystemInfo] $fsinfo, [object] $o) { return "GetBStatic-filesysteminfo-object-string" } - static [object] GetBStatic([System.IO.FileInfo] $finfo, [string] $s) { return "GetBStatic-fileinfo-string-object" } - static [string] GetBStatic([datetime] $d) { return "GetBStatic-datetime-string" } + static [object] GetBStatic([System.IO.FileInfo] $finfo, [string] $s) { return "GetBStatic-fileinfo-string-object" } + static [string] GetBStatic([datetime] $d) { return "GetBStatic-datetime-string" } ## Test enum parameter type [object] GetC([E] $e) { return $e.ToString() } @@ -497,6 +508,8 @@ Describe 'method conversion' -Tags 'CI' { @{ Number = "127d"; Value = "127"; Type = [byte] } @{ Number = "127s"; Value = "127"; Type = [sbyte] } @{ Number = "127y"; Value = "127"; Type = [uint] } + @{ Number = "100n"; Value = "100"; Type = [int] } + @{ Number = "1234s"; Value = "1234"; Type = [bigint] } ) It "Correctly casts to value as type " -TestCases $TestCases { param($Number, $Value, $Type) @@ -521,15 +534,15 @@ 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() } @{ SourceType = [double]; Format = "G15"; ValueScript = { [System.Math]::PI }; StringConversionResult = [System.Math]::PI.ToString("G15"); ToStringResult = [System.Math]::PI.ToString() } - @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 3).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "3.3"; ToStringResult = "3.3000002" } - @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 6).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "6.6"; ToStringResult = "6.6000004" } - @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MaxValue }; StringConversionResult = [float]::MaxValue.ToString("G7"); ToStringResult = [float]::MaxValue.ToString() } - @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MinValue }; StringConversionResult = [float]::MinValue.ToString("G7"); ToStringResult = [float]::MinValue.ToString() } + @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 3).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "3.3"; ToStringResult = "3.3000002" } + @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]$f = 1.1; ($f * 6).ToSingle([cultureinfo]::InvariantCulture) }; StringConversionResult = "6.6"; ToStringResult = "6.6000004" } + @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MaxValue }; StringConversionResult = [float]::MaxValue.ToString("G7"); ToStringResult = [float]::MaxValue.ToString() } + @{ SourceType = [float]; Format = "G7"; ValueScript = { [float]::MinValue }; StringConversionResult = [float]::MinValue.ToString("G7"); ToStringResult = [float]::MinValue.ToString() } ) { param($SourceType, $ValueScript, $StringConversionResult, $ToStringResult) @@ -544,3 +557,66 @@ Describe 'float/double precision when converting to string' -Tags "CI" { $value | Out-String | ForEach-Object -MemberName Trim | Should -BeExactly $StringConversionResult } } + +Describe 'Casting Behaviour of Boolean/Null to Numeral' -Tags CI { + + BeforeAll { + $NullToNumeral = @( + @{ Type = [sbyte]; ExpectedResult = 0y } + @{ Type = [byte]; ExpectedResult = 0uy } + @{ Type = [short]; ExpectedResult = 0s } + @{ Type = [ushort]; ExpectedResult = 0us } + @{ Type = [int]; ExpectedResult = 0 } + @{ Type = [uint]; ExpectedResult = 0u } + @{ Type = [long]; ExpectedResult = 0l } + @{ Type = [ulong]; ExpectedResult = 0ul } + @{ Type = [decimal]; ExpectedResult = 0d } + @{ Type = [float]; ExpectedResult = [float]0 } + @{ Type = [double]; ExpectedResult = 0.0 } + @{ Type = [bigint]; ExpectedResult = 0n } + ) + + $BoolToNumeral = @( + @{ Type = [sbyte]; Value = $true; ExpectedResult = 1y } + @{ Type = [sbyte]; Value = $false; ExpectedResult = 0y } + @{ Type = [byte]; Value = $true; ExpectedResult = 1uy } + @{ Type = [byte]; Value = $false; ExpectedResult = 0uy } + @{ Type = [short]; Value = $true; ExpectedResult = 1s } + @{ Type = [short]; Value = $false; ExpectedResult = 0s } + @{ Type = [ushort]; Value = $true; ExpectedResult = 1us } + @{ Type = [ushort]; Value = $false; ExpectedResult = 0us } + @{ Type = [int]; Value = $true; ExpectedResult = 1 } + @{ Type = [int]; Value = $false; ExpectedResult = 0 } + @{ Type = [uint]; Value = $true; ExpectedResult = 1u } + @{ Type = [uint]; Value = $false; ExpectedResult = 0u } + @{ Type = [long]; Value = $true; ExpectedResult = 1l } + @{ Type = [long]; Value = $false; ExpectedResult = 0l } + @{ Type = [ulong]; Value = $true; ExpectedResult = 1ul } + @{ Type = [ulong]; Value = $false; ExpectedResult = 0ul } + @{ Type = [decimal]; Value = $true; ExpectedResult = 1d } + @{ Type = [decimal]; Value = $false; ExpectedResult = 0d } + @{ Type = [float]; Value = $true; ExpectedResult = [float]1 } + @{ Type = [float]; Value = $false; ExpectedResult = [float]0 } + @{ Type = [double]; Value = $true; ExpectedResult = 1.0 } + @{ Type = [double]; Value = $false; ExpectedResult = 0.0 } + @{ Type = [bigint]; Value = $true; ExpectedResult = 1n } + @{ Type = [bigint]; Value = $false; ExpectedResult = 0n } + ) + } + + It 'should correctly convert $null to as ' -TestCases $NullToNumeral { + param($Type, $ExpectedResult) + + $result = $null -as $Type + $result | Should -Be $ExpectedResult + $result | Should -BeOfType $Type + } + + It 'should correctly convert to as ' -TestCases $BoolToNumeral { + param($Type, $Value, $ExpectedResult) + + $result = $Value -as $Type + $result | Should -Be $ExpectedResult + $result | Should -BeOfType $Type + } +} 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 - $pwd = 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 $pwd - $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/NativeCommandPathUpdate.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandPathUpdate.Tests.ps1 new file mode 100644 index 00000000000..ff90500ac83 --- /dev/null +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandPathUpdate.Tests.ps1 @@ -0,0 +1,373 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +## The "Path Update" feature is only available on Windows. +## Skip the test suite on Unix platforms. +if (-not $IsWindows) { + return; +} + +function GetEnvPathLiteralValue { + param( + [System.EnvironmentVariableTarget] $Target + ) + + if ($Target -eq 'User') { + $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment') + } elseif ($Target -eq 'Machine') { + $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment') + } else { + return [PSCustomObject]@{ Kind = $null; Value = $env:Path } + } + + try { + $kind = $regKey.GetValueKind('Path') + $value = $regKey.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + + return [PSCustomObject]@{ + Kind = $kind + Value = $value + } + } + finally { + ${regKey}?.Dispose() + } +} + +function RestoreEnvPath { + param( + [System.EnvironmentVariableTarget] $Target, + [Microsoft.Win32.RegistryValueKind] $ValueKind, + [string] $LiteralValue + ) + + ## Open the registry key with 'write' access. + if ($Target -eq 'User') { + $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) + } elseif ($Target -eq 'Machine') { + $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true) + } else { + ## Ignore value kind when restoring the in-proc env Path. + $env:Path = $LiteralValue + return + } + + try { + $regKey.SetValue('Path', $LiteralValue, $ValueKind) + } finally { + ${regKey}?.Dispose() + } +} + +function UpdatePackageManager { + param( + [Parameter(ParameterSetName = 'Add')] + [switch] $Add, + + [Parameter(ParameterSetName = 'Remove')] + [switch] $Remove, + + [string] $Name + ) + + $regKeyPath = 'HKLM:\Software\Microsoft\Command Processor\KnownPackageManagers' + $keyExists = Test-Path -Path $regKeyPath + if (-not $keyExists) { + Write-Host -ForegroundColor Cyan "The registry key 'KnownPackageManagers' doesn't exist." + } + + $subKeyPath = "$regKeyPath\$Name" + if ($Add) { + $null = New-Item $subKeyPath -Force -ErrorAction Stop + } + elseif ($Remove -and $keyExists) { + Remove-Item $subKeyPath -Recurse -Force -ErrorAction Stop + } +} + +Describe "Path update for package managers" -tags @('CI', 'RequireAdminOnWindows') { + + It "Path update is off for an executable that is not registered" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + $oldProcPath = $env:Path + + testexe -updateuserandsystempath + + $newUserPath = GetEnvPathLiteralValue -Target 'User' + $newUserPath.Kind | Should -Be $oldUserPath.Kind -Because "Value kind should not be changed" + $newUserPath.Value | Should -BeLike "$($oldUserPath.Value)*X:\not-exist-user-path" -Because "'testexe -updateuserandsystempath' should append 'X:\not-exist-user-path' to the User Path." + + $newSysPath = GetEnvPathLiteralValue -Target 'Machine' + $newSysPath.Kind | Should -Be $oldSysPath.Kind -Because "Value kind should not be changed" + $newSysPath.Value | Should -BeLike "X:\not-exist-sys-path*$($oldSysPath.Value)" -Because "'testexe -updateuserandsystempath' should prepend 'X:\not-exist-sys-path' to the System Path." + + $newProcPath = $env:Path + $newProcPath | Should -Be $oldProcPath -Because "'testexe -updateuserpath' doesn't change the Process Path and the executable 'testexe' is not in the package manager list." + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + ## Add the executable name without extension to the list of package managers. + Context "Add 'testexe' to the list and test 'Path Update'" { + BeforeAll { + UpdatePackageManager -Add -Name 'testexe' + } + + AfterAll { + UpdatePackageManager -Remove -Name 'testexe' + } + + It "Test when only User Path is changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + testexe -updateuserpath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + } + } + + It "Test when only System Path is changed" { + try { + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + testexe -updatesystempath > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-sys-path' + } + } + finally { + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when both User and System Paths are changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + $null = testexe -updateuserandsystempath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path items should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path items should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path;X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path;X:\not-exist-sys-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when neither User nor System Path is changed" { + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## Print help message and exit. + testexe -h > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath | Should -Be $oldProcPath -Because "'testexe -h' doesn't change the env Path." + } + } + + ## Add the executable name with extension to the list of package managers. + Context "Add 'testexe.exe' to the list and test 'Path Update'" { + BeforeAll { + UpdatePackageManager -Add -Name 'testexe.exe' + } + + AfterAll { + UpdatePackageManager -Remove -Name 'testexe.exe' + } + + It "Test when only User Path is changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + testexe -updateuserpath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + } + } + + It "Test when only System Path is changed" { + try { + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + testexe -updatesystempath > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-sys-path' + } + } + finally { + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when both User and System Paths are changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + $null = testexe -updateuserandsystempath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path items should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path items should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path;X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path;X:\not-exist-sys-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when neither User nor System Path is changed" { + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## Print help message and exit. + testexe -h > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath | Should -Be $oldProcPath -Because "'testexe -h' doesn't change the env Path." + } + } +} diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandProcessor.Tests.ps1 index 12b1349f716..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 } @@ -140,6 +200,25 @@ Describe "Native Command Processor" -tags "Feature" { [Console]::OutputEncoding = $originalOutputEncoding } } + + 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' + } + + It 'Can start an elevated associated process correctly' -Skip:( + !$IsWindows -or (!(Test-Path (Join-Path -Path $env:windir -ChildPath 'system32' -AdditionalChildPath 'diskmgmt.msc'))) + ) { + # test bug https://github.com/PowerShell/PowerShell/issues/13744 where console is blocked + diskmgmt.msc + 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") { @@ -233,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 2970f7ae7a9..53ed45fe4da 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeStreams.Tests.ps1 @@ -12,17 +12,16 @@ 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 # this check should be the first one, because $error is a global shared variable It 'should not add records to $error variable' { - # we are keeping existing Windows PS v5.1 behavior for $error variable - $error.Count | Should -Be 9 + $error.Count | Should -Be 0 } It 'uses ErrorRecord object to return stderr output' { @@ -54,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..3a478607c08 --- /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 + } + + 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: